6.8 condition_variable、wait,notify_one 、notify_all

1.条件变量std:: condition_variable、wait()、notify_one()

std:: condition_variable实际上是个类,是一个与条件相关的类,说白了就是等待一个条件的达成。这个类是需要和互斥量来配合工作的,用的时候我们要生成这个类的对象。

实例代码:

线程A:等待一个条件满足

线程B:专门往消息队列扔消息(数据)

 

 实例代码如下:(解析在代码注释中)

代码的主体,可以看看之前的博客,做一下了解:

把数据从消息队列取出的线程,需要不断加锁判断,如果是空的,就会不停的加锁解锁造成效率的降低。这就是上一篇博客双重锁定的由来,有一定的提升效率。但是现在这里就引入了wait(),当为空的时候就在这里等待。

wait()用来等一个东西
1.如果第二个参数lambda表达式返回值是true,那么wait()直接返回;
如果第二个参数lambda表达式返回值是false,那么wait()将解锁互斥量,并且堵塞到本行
那堵到什么时候为止?堵塞到其他线程调用notify_one()成员函数为止。
2.如果wait()没有第二个参数:my_cond.wait(sbguard);那么就跟第二个参数返回false效果一样,那么wait()将解锁互斥量,并且堵塞到本行,堵塞到其他线程调用notify_one()成员函数为止。
3.但其他线程用notify_one()将本wait(原来是睡着/堵塞)的状态唤醒后,wait()就开始恢复干活了,那恢复后的wait()干什么活?
a)wait()不断的尝试重新获取互斥量锁,如果获取不到,那么流程就卡在wait()这里等着获取,如果获取到了互斥锁,那么wait()就继续执行b
b)上锁(实际上获取锁了就等于上了锁)
b.1)如果wait有第二个参数(lamdba),就判断这个lamdba表达式,如果表达式为false,
那么wait()又对互斥量解锁然后又休眠,这里继续等待再次被notify_one()唤醒
b.2)如果lamdba表达式为true,则wait()返回,流程走下来(此时互斥锁被锁着)
b.3)如果wait()没有第二个参数,则wait()返回,流程走下来

为防止虚假唤醒:wait()中要有第二个参数(lambda)并且这个lambda中要正确处理公共数据是否存在

 

代码还有一点瑕疵:

1.入队列的10000次循环执行完之后,就不会再执行notify_one(),就会一直wait()。这里只是一个演示,不影响。

2.入队列循环的时候,可能一直获得锁,出队列的获得锁的机会可能非常的小。 

3.还有一点,出队列的锁释放之后,可能处理其它的业务时间会很久。这样入队列在不断的notify_one(),但是出队列的线程却没有在wait(),这就会导致此次的唤醒没有效果。

 

#include<iostream>
#include<thread>
#include<string>
#include<vector>
#include<list>
#include<mutex>
 
using namespace std;
 
class A
{
public:
	void inMsgRecvQueue()
	{
		for (int i = 0; i < 10000; i++)
		{
			cout << "inMsgRecvQueue()执行,插入一个元素" << i << endl;
 
			std::unique_lock<std::mutex> sbguard(my_mutex);
			msgRecvQueue.push_back(i);
 
			//假如outMsgRecvQueue()正在处理一个事务,需要一段时间,而不是正卡在wait()那里等待你的唤醒,
			//那么此时这个notify_one()这个调用也许就没效果;
			my_cond.notify_one();//我们尝试把wait()线程唤醒,执行完这行,那么outMsgRecvQueue()里面的wait()就会被唤醒
			                       //唤醒之后的事情后续研究;
			//my_cond.notify_all(); //通知所有线程
			//...
			//其他处理代码
		}
	}
 
	//把数据从消息队列取出的线程
	void outMsgRecvQueue()
	{
		int command = 0;
		
		while (true)
		{
			std::unique_lock<std::mutex> sbguard(my_mutex);
 

			my_cond.wait(sbguard, [this] {       //一个lambda就是一个可调用对象(函数)
				if (!msgRecvQueue.empty())
					return true;
				return false;
			});
	//流程能走到 这里来,这个互斥锁一定是锁着的。同时msgRecvQueue至少有一条数据的
			command = msgRecvQueue.front();
			msgRecvQueue.pop_front();      
			sbguard.unlock();       //因为unique_lock的灵活性,所以我们可以随时解锁,以免锁住太长时间,这样就可以让其它的线程获得锁。
 
			cout << "outMsgRecvQueue()执行,取出一个元素" << command << endl;

     //这里可能指执行一些其他的业务。时间肯呢个会很长。
		}
	}
 
private:
	std::list<int> msgRecvQueue;//容器(消息队列),代表玩家发送过来的命令。
	std::mutex my_mutex;//创建一个互斥量(一把锁)
	std::condition_variable my_cond;//生成一个条件变量对象
};
 
int main()
{
	A myobja;
 
	std::thread myOutMsgObj(&A::outMsgRecvQueue, &myobja);
	std::thread myOutMsgObj2(&A::outMsgRecvQueue, &myobja);
 
	std::thread myInMsgObj(&A::inMsgRecvQueue, &myobja);
 
	myOutMsgObj.join();
	myOutMsgObj2.join();
 
	myInMsgObj.join();
 
	cout << "主线程执行!" << endl;
 
	return 0;
}

2.代码深思考

1.第一次,出队列的线程获得锁时,队列中的消息可能就是几百个了。那么我们处理的时候,就不应该再是一条的处理。

2.wait()被唤醒之后,再次尝试拿锁,可能仍然不成功。竞争锁的问题。

3.notify_one()  可能掉用之后,就没作用。

虚假唤醒:

wait中要有第二参数(lambda)并且这个lambda中要正确判断要处理的公共数据是否存在

wait , notify_ont/notify_all

notify_all(),一个线程取到了数据,另一个线程唤醒了但是取不到数据了(容器为空),这就是虚假唤醒

3.notify_all()

notify_one()只能通知一个线程;

notify_ all()通知所有线程,但是在本例中差异不大;

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值