C++并发与多线程(七) condition_variable、wait、notify_one、notify_all

条件变量condition_variable

condition_variable是一个和条件相关的类,本质上就是等待一个条件达成。使用的时候必须和互斥量mutex配合使用。

使用场景:增加效率

//把消息从消息队列取出
	void outMsgRecvQueue() {
		int command = 0; //指令为command;
		for (int i = 0; i < 10000; i++) {
			bool result = outMsgLULProc(command); //将所有对共享数据的访问都封装成一个函数,方便加锁
			if (result) {
				//消息队列不为空
				//对根据命令对数据进行处理
			}
			else {
				//消息队列为空
				cout << "消息队列为空!" << endl;
			}
		}
	}
	
	bool outMsgLULProc(int &command) {
		//对共享资源上锁
		unique_lock<mutex> myUnique2(m_mutex2);
		if (!msgRecvQueue.empty()) {
			//消息不为空
			int command = msgRecvQueue.front(); //返回第一个元素,但不检查是否存在
			msgRecvQueue.pop_front();

			return true;
		}
		return false;
	}

上面是(五)中写的取消息队列的部分代码,outMsgRecvQueue中不停的循环,调用outMsgLULProc查看队列是否为空,只有消息不为空,才会进行操作,CPU被极大的浪费在了查看队列是否为空上;(六)中提到的双重加锁,可以一定程序上减少判断,提高效率,这里利用条件变量可以更加提高效率:

一、outMsgRecvQueue调用wait()

outMsgLULProc修改成一直等待,消息队列中有数据的时候,来通知线程,线程再来取数据操作,这样就避免了需要CPU把资源浪费到一直判断消息是否为空上。

//把消息从消息队列取出
	void outMsgRecvQueue() {
		int command = 0;
		while (true) {
			unique_lock<mutex> myUnique(m_mutex2);
			m_cond.wait(myUnique, [this] { //一个lamda表达式就是一个可调用对象(函数)
				if (!msgRecvQueue.empty()) {
					return true;
				}
				else
				{
					return false;
				}
			});
			//未完,待续
		}
	}
//...
	condition_variable m_cond; //生成一个条件变量对象

wait()方法是用来等待一个条件

  1. 如果第二个参数的lambda表达式返回值是false,那么wait()将解锁互斥量,并阻塞到本行

  2. 如果第二个参数的lambda表达式返回值是true,那么wait()直接返回并继续执行。

  3. 阻塞的时机为,一直阻塞到**其他某个线程调用notify_one()**成员函数为止;

  4. 如果没有第二个参数,那么效果跟第二个参数lambda表达式返回false效果一样

二、inMsgRecvQueue调用notice_one()

继续修改inMsgRecvQueue,增加调用notify_one()成员函数:

	//把收到的消息加入消息队列
	void inMsgRecvQueue() {
		for (int i = 0; i < 10000; i++) {
			cout << "插入一个元素到消息队列";
			//互斥量mutex的使用,保护共享资源
			unique_lock<mutex> myUnique1(m_mutex1);
			msgRecvQueue.push_back(i);

			m_cond.notify_one(); //去把wait的线程唤醒,执行完这一行,outMsgRecvQueue中的wait会被唤醒
								 //唤醒之后继续他的操作
			//其它处理步骤...
		}
	}

三、outMsgRecvQueue的wait被notice_one唤醒后

  1. wait()不断尝试获取互斥量锁,如果获取不到那么流程就卡在wait()这里等待获取,如果获取到了,那么wait()就继续执行,这也表明获取到了锁。(也就是说,只要执行到了wait后面的代码,此线程一定获取到了锁

  2. 如果wait有第二个参数就判断这个lambda表达式。

2.1 如果表达式为false,那wait又对互斥量解锁,然后又休眠,等待再次被notify_one()唤醒
2.2 如果lambda表达式为true,则wait返回,流程可以继续执行(此时互斥量已被锁住)。

  1. 如果wait没有第二个参数,则wait返回,流程走下去。

完整代码:

class MyPrint {
public:
	//把收到的消息加入消息队列
	void inMsgRecvQueue() {
		for (int i = 0; i < 10000; i++) {
			cout << "插入一个元素到消息队列";
			//互斥量mutex的使用,保护共享资源
			unique_lock<mutex> myUnique1(m_mutex1);
			msgRecvQueue.push_back(i);
			m_cond.notify_one(); //去把wait的线程唤醒,执行完这一行,outMsgRecvQueue中的wait会被唤醒
								 //唤醒之后继续他的操作
			//其它处理步骤...
		}
	}
	//把消息从消息队列取出
	void outMsgRecvQueue() {
		int command = 0;
		while (true) {
			unique_lock<mutex> myUnique(m_mutex2);
			//当其它线程用notice_one将wait唤醒
			m_cond.wait(myUnique, [this] { //一个lamda表达式就是一个可调用对象(函数)
				if (!msgRecvQueue.empty()) {
					return true;
				}
				else
				{
					return false;
				}
			});
			//wait后,队列一定不为空,一定获取到了锁
			command = msgRecvQueue.front(); //返回第一个元素,但不检查是否存在
			msgRecvQueue.pop_front();

		}
	}

	list<int> msgRecvQueue; //消息队列
	//死锁演示
	mutex m_mutex1;
	mutex m_mutex2;

	condition_variable m_cond; //生成一个条件变量对象
};

PS:重点是理解wait被notice_one唤醒之后的流程。

深入思考

上面的代码可能导致出现一种情况:
事实上执行的时候,不一定是inMsgRecvQueue()outMsgRecvQueue()你执行一次,我执行一次,这么简单,两个进程获取锁,都是概率成功的,竞争锁的时候,谁获得和调度算法有关,都是可能成功的。

因为outMsgRecvQueue()inMsgRecvQueue()并不是一对一执行的,所以当程序循环执行很多次以后,可能在消息队列中已经有了很多消息,但是,outMsgRecvQueue还是被唤醒一次只处理一条数据。这时可以考虑把outMsgRecvQueue多执行几次,或者对inMsgRecvQueue进行限流。

notice_all()

notify_one():通知一个线程的wait()

notify_all():通知所有线程的wait()

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值