C++详解如何实现两个线程交替打印

C++线程库,点击此处查看文档

首先简单搭一个框架,让两个线程先尝试实现交替打印。

//实现两个线程交替打印
#include <iostream>
#include <thread>
using namespace std;
int main(void)
{
	int n = 100;
	int i = 0;
	//创建两个线程
	thread t1([&n, &i](){
		while (i < n)
		{
			cout << i << " ";
			i++;
		}
	});
	thread t2([&n, &i]() {
		while (i < n)
		{
			cout << i << " ";
			i++;
		}
	});
	if (t1.joinable())
	{
		t1.join();
	}
	if (t2.joinable())
	{
		t2.join();
	}
	return 0;
}

为了让我们更加清楚是哪个线程打印了,我们需要获取线程的ID。

#include <iostream>
#include <thread>
using namespace std;
int main(void)
{
	int n = 100;
	int i = 0;
	//创建两个线程
	thread t1([&n, &i](){
		while (i < n)
		{
			cout << this_thread::get_id()  << ": " << i << endl;
			i++;
		}
	});
	thread t2([&n, &i]() {
		while (i < n)
		{
			cout << this_thread::get_id() << ": " << i << endl;
			i++;
		}
	});
	if (t1.joinable())
	{
		t1.join();
	}
	if (t2.joinable())
	{
		t2.join();
	}
	return 0;
}

这显然没有完成两个线程交替打印的目的,甚至数据的打印都非常地乱。这是因为i是临界资源,多个线程争抢访问临界资源可能会造成数据二义,线程是不安全的,需要保证任意时刻只有一个线程能够访问临界资源。

所以创建一个互斥量,并在临界区合适的地方加锁和解锁。由于线程的执行函数我使用了lambda表达式,为了让两个线程使用的是同一把锁,把锁创建在了main函数内,并在lambda表达式内使用了引用捕捉。

#include <iostream>
#include <thread>
#include <mutex>
using namespace std;
int main(void)
{
	int n = 100;
	int i = 0;
	mutex mtx;
	//创建两个线程
	thread t1([&n, &i, &mtx](){
		while (i < n)
		{
			mtx.lock();
			cout << this_thread::get_id()  << ": " << i << endl;
			i++;
			mtx.unlock();
		}
	});
	thread t2([&n, &i, &mtx]() {
		while (i < n)
		{
			mtx.lock();
			cout << this_thread::get_id() << ": " << i << endl;
			i++;
			mtx.unlock();
		}
	});
	if (t1.joinable())
	{
		t1.join();
	}
	if (t2.joinable())
	{
		t2.join();
	}
	return 0;
}

在C++中,一般不直接操作锁,而是由类去管理锁。

//第一个管理锁的类
template <class Mutex> class lock_guard;
//第二个管理锁的类
template <class Mutex> class unique_lock;

lock_guar类,只有构造和析构函数。一般用于加锁和解锁,这里进行简单的模拟:

//注意:为了使得加锁和解锁的是同一把锁
//需要使用引用
template <class Lock>
class LockGuard
{
public:
	LockGuard(Lock &lck)
		:_lock(lck)
	{
		_lock.lock();
	}
	~LockGuard()
	{
		_lock.unlock();
	}
private:
	Lock &_lock;
};

unique_lock的成员方法就不仅仅是析构函数和构造函数。详见文档unique_lock介绍和使用

这里将锁交给unique_lock类的对象进行管理。

int main(void)
{
	int n = 100;
	int i = 0;
	mutex mtx;
	//创建两个线程
	thread t1([&n, &i, &mtx, &cv, &flag](){
		while (i < n)
		{
			unique_lock<mutex> LockManage(mtx);
			cout << this_thread::get_id()  << ": " << i << endl;
			i++;
		}
	});
	thread t2([&n, &i, &mtx, &cv, &flag]() {
		while (i < n)
		{
			unique_lock<mutex> LockManage(mtx);
			cout << this_thread::get_id() << ": " << i << endl;
			i++;
		}
	});
	if (t1.joinable())
	{
		t1.join();
	}
	if (t2.joinable())
	{
		t2.join();
	}
	return 0;
}

线程是安全了,但如果其中一个线程竞争锁的能力比较强,那么可能会出现上面这种情况。

需要控制:一个线程执行一次后,如果再次去执行就不准许了,同时可以唤醒另一个进程去执行,如此循环往复达到交替打印的目的。所以可以增加一个条件变量,让某个线程在该条件变量下的阻塞队列等待。

C++库中线程在条件变量下的等待函数第一个参数注意是管理锁的类对象

int main(void)
{
	int n = 100;
	int i = 0;
	mutex mtx;
	condition_variable cv;
	bool flag = false;
	//创建两个线程
	thread t1([&n, &i, &mtx, &cv, &flag](){
		while (i < n)
		{
			unique_lock<mutex> LockManage(mtx);
			//!flag为真,那么获取后不会阻塞,优先运行
			cv.wait(LockManage, [&flag]() {return !flag; });
			cout << this_thread::get_id()  << ": " << i << endl;
			i++;
		}
	});
	thread t2([&n, &i, &mtx, &cv, &flag]() {
		while (i < n)
		{
			unique_lock<mutex> LockManage(mtx);
			//flag为假,竞争到锁后,由于条件不满足,阻塞
			cv.wait(LockManage, [&flag]() {return flag; });
			cout << this_thread::get_id() << ": " << i << endl;
			i++;
		}
	});
	if (t1.joinable())
	{
		t1.join();
	}
	if (t2.joinable())
	{
		t2.join();
	}
	return 0;
}

这里flag以及lambda表达式的增加是非常巧妙的。flag的初始化值为false,让线程t2在[&flag]() {return false; }下等待,那么t2线程就会先执行。

线程t1竞争到了锁,但是由于不满足条件,会继续等待,所以就出现了上面的情况。

需要一个线程唤醒另一个线程之前,将flag的值进行修改。

int main(void)
{
	int n = 100;
	int i = 0;
	mutex mtx;
	condition_variable cv;
	bool flag = false;
	//创建两个线程
	thread t1([&n, &i, &mtx, &cv, &flag](){
		while (i < n)
		{
			unique_lock<mutex> LockManage(mtx);
			//!flag为真,那么获取后不会阻塞,优先运行
			cv.wait(LockManage, [&flag]() {return !flag; });
			cout << this_thread::get_id()  << ": " << i << endl;
			i++;
			flag = true;
			cv.notify_one();
		}
	});
	thread t2([&n, &i, &mtx, &cv, &flag]() {
		while (i < n)
		{
			unique_lock<mutex> LockManage(mtx);
			//flag为假,竞争到锁后,由于条件不满足,阻塞
			cv.wait(LockManage, [&flag]() {return flag; });
			cout << this_thread::get_id() << ": " << i << endl;
			i++;
			flag = false;
			cv.notify_one();
		}
	});
	if (t1.joinable())
	{
		t1.join();
	}
	if (t2.joinable())
	{
		t2.join();
	}
	return 0;
}

最终,实现了两个线程交替打印(一个线程打印奇数、一个线程打印偶数)

到此这篇关于C++详解如何实现两个线程交替打印的文章就介绍到这了,更多相关C++线程交替打印内容请搜索我们以前的文章或继续浏览下面的相关文章希望大家以后多多支持我们!

(0)

相关推荐

  • C++11用两个线程轮流打印整数的实现方法

    使用C++11标准的的线程语法,用两个线程轮流打印整数,一个线程打印奇数,一个线程打印偶数.可以练习线程的基本操作.线程锁和条件变量等技术.完整代码如下.代码后面附有主要语句的讲解. #include <thread> #include <iostream> #include <mutex> #include <condition_variable> std::mutex data_mutex; std::condition_variable data_va

  • C++详解如何实现两个线程交替打印

    C++线程库,点击此处查看文档 首先简单搭一个框架,让两个线程先尝试实现交替打印. //实现两个线程交替打印 #include <iostream> #include <thread> using namespace std; int main(void) { int n = 100; int i = 0; //创建两个线程 thread t1([&n, &i](){ while (i < n) { cout << i << "

  • java实现两个线程交替打印的实例代码

    使用ReentrantLock实现两个线程交替打印 实现字母在前数字在后 package com.study.pattern; import java.util.concurrent.CountDownLatch; import java.util.concurrent.locks.Condition; import java.util.concurrent.locks.Lock; import java.util.concurrent.locks.ReentrantLock; public c

  • Java 用两个线程交替打印数字和字母

    前一段时间听马士兵老师讲课,讲到某公司的一个面试,两个线程,其中一个线程输出ABC,另一个线程输出123,如何控制两个线程交叉输出1A2B3C,由于本人多线程掌握的一直不是很好,所以听完这道题,个人感觉收获良多,这是一个学习笔记.这道题有多种解法,不过有些属于纯炫技,所以只记录常见的三种解法.首先看第一种 1. park 和 unpark package cn.bridgeli.demo;   import com.google.common.collect.Lists;   import ja

  • 详解java各种集合的线程安全

    线程安全 首先要明白线程的工作原理,jvm有一个main memory,而每个线程有自己的working memory,一个线程对一个variable进行操作时,都要在自己的working memory里面建立一个copy,操作完之后再写入main memory.多个线程同时操作同一个variable,就可能会出现不可预知的结果.根据上面的解释,很容易想出相应的scenario. 而用synchronized的关键是建立一个monitor,这个monitor可以是要修改的variable也可以其

  • 详解C++11中的线程锁和条件变量

    线程 std::thread类, 位于<thread>头文件,实现了线程操作.std::thread可以和普通函数和 lambda 表达式搭配使用.它还允许向线程的执行函数传递任意多参数. #include <thread> void func() { // do some work } int main() { std::thread t(func); t.join(); return 0; } 上面的例子中,t是一个线程实例,函数func()在该线程运行.调用join()函数是

  • 详解C++11中的线程库

    目录 一.线程库的介绍 1.1. 使用时的注意点 1.2. 线程函数参数 1.3. join与detach 二.原子性操作库 2.1. atomic 2.2. 锁 三.使用lambda表达式创建多个线程 四.条件变量 一.线程库的介绍 在C++11之前,涉及到多线程问题,都是和平台相关的,比如windows和linux下各有自己的接口,这使得代码的可移植性比较差.C++11中最重要的特性就是对线程进行支持了,使得C++在并行编程时不需要依赖第三方库,而且在原子操作中还引入了原子类的概念.要使用标

  • 详解Java8合并两个Map中元素的正确姿势

     1. 介绍 本入门教程将介绍Java8中如何合并两个map. 更具体说来,我们将研究不同的合并方案,包括Map含有重复元素的情况. 2. 初始化 我们定义两个map实例 private static Map<String, Employee> map1 = new HashMap<>(); private static Map<String, Employee> map2 = new HashMap<>(); Employee类 public class

  • 详解Java中两种分页遍历的使用姿势

    在日常开发中,分页遍历迭代的场景可以说非常普遍了,比如扫表,每次捞100条数据,然后遍历这100条数据,依次执行某个业务逻辑:这100条执行完毕之后,再加载下一百条数据,直到扫描完毕 那么要实现上面这种分页迭代遍历的场景,我们可以怎么做呢 本文将介绍两种使用姿势 常规的使用方法 借助Iterator的使用姿势 1. 数据查询模拟 首先mock一个分页获取数据的逻辑,直接随机生成数据,并且控制最多返回三页 public static int cnt = 0; private static List

  • 详解react的两种动态改变css样式的方法

    第一种:动态添加class,以点击按钮让文字显示隐藏为demo import React, { Component, Fragment } from 'react'; import './style.css'; class Demo extends Component{ constructor(props) { super(props); this.state = { display: true } this.handleshow = this.handleshow.bind(this) thi

  • 详解基于MybatisPlus两步实现多租户方案

    1.定义一个TenantLineHandler的实现类: import com.baomidou.mybatisplus.extension.plugins.handler.TenantLineHandler; import com.google.common.collect.Lists; import net.sf.jsqlparser.expression.Expression; import net.sf.jsqlparser.expression.LongValue; import ja

随机推荐