8.condition_variable、wait、notify_one、notify_all

目录

在这里插入图片描述

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

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

实例代码:

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

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

1.1 wait()

wait()用来等一个东西

  1. 如果第二个参数是true,那么wait()直接返回
  2. 如果第二个参数lambda表达式返回值是false,那么wait()将解锁互斥量,并堵塞到本行;那堵塞到什么时候为止呢?堵塞到其他某个线程调用notify_one()成员函数为止;
  3. 如果wait()没有第二个参数,my_cond.wait(sbguard),那么就跟第二个参数lambda表达式返回false效果一样

1.2 notify_one()+wait()的工作流程

1、当其他线程用notify_one()将本wait(原本是睡着/堵塞)的状态唤醒后,wait就开始恢复干活了,恢复后的wait干什么活?
a)wait不断地尝试重新获取互斥量锁,如果获取不到,那么流程就卡在wait这里等着获取,如果获取到,那么wait就继续执行b
b)上锁(实际上获取到了锁,就等于上了锁)
b.1)如果wait有第二个参数(lambda),就判断这个lamda表达式,如果lambda表达式为false那么wait又对互斥量解锁,然后又休眠,再等待被notify_one()唤醒
b.2)如果lambda为true,则wait返回,流程走下来
b.3)如果wait()没有第二个参数,则wait()返回,流程走下来

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

#include <iostream>
#include <thread>
#include <vector>
#include <list>
#include <string>
#include <mutex>

using namespace std;


class A {
public:
	//把收到的信息(玩家命令) 排到一个队列的线程
	void inMsgRecvQueue() {
		for (int i = 0; i < 100000; ++i) {		
			cout << "inMsgRecvQueue()执行,插入一个元素" << endl;	

			std::lock_guard<std::mutex> sbguard(my_mutex);
			msgRecvQueue.push_back(i);   //假设这个数字i就是我收到的命令,我直接弄到消息队列里边
            
            
			//假如outMsgRecvQueue()正在处理一个事务,需要一段时间,而不是正卡在wait()那里等待你的唤醒,
			//那么此时这个notify_one()这个调用也许就没效果;
			my_cond.notify_one(); //我们尝试把wait()的线程唤醒,执行完这行,那么outMsgRecvQueue()里边的wait就会被唤醒
								  //唤醒之后的事情后续研究


			//..
			//处理其他代码
		}
		return;
	}

	//把数据从消息队列中取出的线程
	void outMsgRecvQueue() {
		int command = 0;
		while (true)
		{
			unique_lock<mutex> sbguard(my_mutex);

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

			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 mytobj;
	std::thread myOutMsgobj(&A::outMsgRecvQueue, &mytobj);   //第二个参数是引用,这样就不是复制一份了,但是就不能用detach了
	std::thread myInMsgobj(&A::inMsgRecvQueue, &mytobj);

	myOutMsgobj.join();
	myInMsgobj.join();//两个谁先谁后无所谓


	return 0;
}

二、上述条件深入思考

notify_one()与wait()怎样工作的,想想流程

三、notify_all()

#include <iostream>
#include <thread>
#include <vector>
#include <list>
#include <string>
#include <mutex>

using namespace std;


class A {
public:
	//把收到的信息(玩家命令) 排到一个队列的线程
	void inMsgRecvQueue() {
		for (int i = 0; i < 100000; ++i) {		
			cout << "inMsgRecvQueue()执行,插入一个元素" << endl;	

			std::lock_guard<std::mutex> sbguard(my_mutex);
			msgRecvQueue.push_back(i);   //假设这个数字i就是我收到的命令,我直接弄到消息队列里边
            
            
			//假如outMsgRecvQueue()正在处理一个事务,需要一段时间,而不是正卡在wait()那里等待你的唤醒,
			//那么此时这个notify_one()这个调用也许就没效果;
			//my_cond.notify_one(); //我们尝试把wait()的线程唤醒,执行完这行,那么outMsgRecvQueue()里边的wait就会被唤醒
								  //唤醒之后的事情后续研究

			my_cond.notify_all(); //通知所有线程
			//..
			//处理其他代码
		}
		return;
	}




	//把数据从消息队列中取出的线程
	void outMsgRecvQueue() {
		int command = 0;
		while (true)
		{
			unique_lock<mutex> sbguard(my_mutex);

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

			my_cond.wait(sbguard, [this] { //一个lambda表达式就是一个可调用对象(相当于一个函数)
				if (!msgRecvQueue.empty()) 
					return true;
				return false;
			});
            
			//流程能走到 这里来,这个互斥锁一定是锁着的。同时msgRecvQueue至少有一条数据的
			command = msgRecvQueue.front();
			msgRecvQueue.pop_front();      
			sbguard.unlock();              //因为unique_lock的灵活性,所以我们可以随时解锁,以免锁住太长时间

		}

	}

private:
	std::list<int> msgRecvQueue; //容器,专门用来代表玩家给咱们发过来的命令。这里就相当于是共享内存
	std::mutex my_mutex;//创建一个互斥量
	std::condition_variable my_cond; //生成一个条件变量对象
};



int main() {
	//准备用成员函数作为线程函数的方法来写线程;
	A mytobj;
	std::thread myOutMsgobj(&A::outMsgRecvQueue, &mytobj);   //第二个参数是引用,这样就不是复制一份了,但是就不能用detach了
	std::thread myInMsgobj(&A::inMsgRecvQueue, &mytobj);

	myOutMsgobj.join();
	myInMsgobj.join();//两个谁先谁后无所谓


	return 0;
}
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值