condition_variable、wait、 notify_one、 notify_all
)
condition_variable、wait、 notify_one、 notify_all
一 条件变量 std: condition_variable, wait(),notify _one()
1.condition_variable
condition_variable my_cond; //生成一个条件变量对象
std::condition_variable 是一个类,是一个和条件相关的一个类,就是等待一个条件达成,和互斥锁搭配使用,该类对象具有成员函数wait()和notify_one(),配合使用。
2.wait()
wait()用来等一个东西
参数一:需要配合的互斥锁
参数二:lambda表达式,即唤醒或睡眠wait()的条件
如果第二个参数即lambda的返回值为false,表面无法满足wait()下面的程序的条件,那么wait()将解锁互斥量,使生产者去生产任务,wait()堵塞到本行,即睡眠状态,一直到其他某个线程调用notify_one()激活wait(),激活wait(),wait()首先抢锁,抢到锁继续判断第二个参数的返回值,如果是false则继续解锁并睡眠,直到返回true,执行接下来的流程。
如果lambda返回true,那么wait()直接返回,执行wait()下面的操作。
如果wait没有第二个参数,那么和lambda默认返回false效果一样
condition_variable my_cond;
//读取命令的线程
void outMsgRecvQueue()
{
int command = 0;
while (true)
{
std::unique_lock<std::mutex> sbguard1(my_mutex1);
//wait()用来等一个东西
//如果第二个参数即lambda的返回值为false,那么wait将解锁互斥量,并堵塞到本行
//如果lambda返回true,那么wait()直接返回
// 一直到其他某个线程调用notify_one()函数为止。
//如果wait没有第二个参数,那么和lambda默认返回false效果一样
my_cond.wait(sbguard1,[this]{
if (!msgRecvQueue.empty())
return true;
return false;
});
}
}
3.notify _one()
将一个wait唤醒,表示已经满足了消费者所需要的条件,激活wait进行后续拿锁和判断的操作。
void inMsgRecvQueue()
{
for (int i = 0; i < 100000; i++)
{
{
cout << "inMsgRecvQueue insert:" << i << endl;
std::unique_lock<std::mutex>sbguard1 = rtn_unique_lock();
msgRecvQueue.push_back(i); //假设数字i为命令 放入队列
my_cond.notify_one(); //尝试把wait的线程唤醒,执行本行,则outMsgRecvQueue里边的wait就会被唤醒
}
}
}
当其他线程用notify_one将此wait的状态状态唤醒后,wait开始做事
(1) 不断尝试获取互斥量锁,如果获取不到,则流程卡在wait不断获取互斥锁,如果获取到则wait继续执行上锁,实际上获取到锁就是上锁
(2.1)如果wait有第二个参数,则判断这个lambda,如果为false,那么wait又解锁,堵塞到本行,然后再休眠,等待再次被唤醒
(2.2)如果lambda返回为true,则wait返回,流程走下来,互斥锁还是锁定的
(2.3)如果没有第二个参数,则wait返回,流程走下去
流程走到下一行,执行具有互斥锁保护的操作,等待整个流程执行完毕,unique_lock会自动解锁,或者手动提前解锁,然后执行不需要保护的操作。
//读取命令的线程
void outMsgRecvQueue()
{
int command = 0;
while (true)
{
std::unique_lock<std::mutex> sbguard1(my_mutex1);
//wait()用来等一个东西
//如果第二个参数即lambda的返回值为false,那么wait将解锁互斥量,并堵塞到本行
//如果lambda返回true,那么wait()直接返回
// 一直到其他某个线程调用notify_one()函数为止。
//如果wait没有第二个参数,那么和lambda默认返回false效果一样
//当其他线程用notify_one将此wait的状态状态唤醒后,wait开始做事
//(1) 不断尝试获取互斥量锁,如果获取不到,则流程卡在wait不断获取互斥锁,如果获取到则wait继续执行上锁,实际上获取到锁就是上锁
//(2.1)如果wait有第二个参数,则判断这个lambda,如果为false,那么wait又解锁,堵塞到本行,然后再休眠,等待再次被唤醒
//(2.2)如果lambda返回为true,则wait返回,流程走下来,互斥锁还是锁定的
//(2.3)如果没有第二个参数,则wait返回,流程走下去
my_cond.wait(sbguard1,[this]{
if (!msgRecvQueue.empty())
return true;
return false;
});
//流程走到这里,互斥锁一定是锁着的,msgRecvQueue里面肯定是有数据的
command = msgRecvQueue.front();
msgRecvQueue.pop_front();
sbguard1.unlock();//提前解锁,因为unique_lock的灵活性,所以我们可以提前解锁,执行后续操作
cout<<"outMsgRecvQueue get :"<<command<<endl;
}
}
整体代码
#include <iostream>
#include <vector>
#include <thread>
#include <list>
#include <mutex>
#include <condition_variable>
using namespace std;
class A
{
public:
//把玩家命令放入到一个队列的进程
std::unique_lock<std::mutex>rtn_unique_lock()
{
std::unique_lock<std::mutex>temguard(my_mutex1);
return temguard;
//从函数返回一个局部的unique_lock对象是可以的,移动构造函数
//返回这种局部对象temguard会导致系统生成临时unique_lock对象,并调用unique_lock的移动构造函数
}
void inMsgRecvQueue()
{
for (int i = 0; i < 5; i++)
{
{
cout << "inMsgRecvQueue insert:" << i << endl;
std::unique_lock<std::mutex>sbguard1 = rtn_unique_lock();
msgRecvQueue.push_back(i); //假设数字i为命令 放入队列
my_cond.notify_one(); //尝试把wait的线程唤醒,执行本行,则outMsgRecvQueue里边的wait就会被唤醒
}
}
}
//读取命令的线程
void outMsgRecvQueue() {
int command = 0;
while (true) {
std::unique_lock<std::mutex> sbguard1(my_mutex1);
//wait()用来等一个东西
//如果第二个参数即lambda的返回值为false,那么wait将解锁互斥量,并堵塞到本行
//如果lambda返回true,那么wait()直接返回
// 一直到其他某个线程调用notify_one()函数为止。
//如果wait没有第二个参数,那么和lambda默认返回false效果一样
//当其他线程用notify_one将此wait的状态状态唤醒后,wait开始做事
//(1) 不断尝试获取互斥量锁,如果获取不到,则流程卡在wait不断获取互斥锁,如果获取到则wait继续执行上锁,实际上获取到锁就是上锁
//(2.1)如果wait有第二个参数,则判断这个lambda,如果为false,那么wait又解锁,堵塞到本行,然后再休眠,等待再次被唤醒
//(2.2)如果lambda返回为true,则wait返回,流程走下来,互斥锁还是锁定的
//(2.3)如果没有第二个参数,则wait返回,流程走下去
my_cond.wait(sbguard1, [this] {
if (!msgRecvQueue.empty())
return true;
return false;
});
//流程走到这里,互斥锁一定是锁着的,msgRecvQueue里面肯定是有数据的
command = msgRecvQueue.front();
msgRecvQueue.pop_front();
sbguard1.unlock();//提前解锁,因为unique_lock的灵活性,所以我们可以提前解锁
cout<<"outMsgRecvQueue get :"<<command<<endl;
}
}
private:
std::list<int>msgRecvQueue;
mutex my_mutex1; //创建一个互斥量
condition_variable my_cond; //生成一个条件变量对象
};
int main()
{
A myobja;
thread myOutnMsg(&A::outMsgRecvQueue, &myobja);
thread myInMsgObj(&A::inMsgRecvQueue, &myobja);
myOutnMsg.join();
myInMsgObj.join();
}
二 上述代码深入思考
关键点:
(1)wait被唤醒后,还是需要拿锁,而且wait并不一定拿到锁,因为生产者也一直在拿锁,如果多个消费者,则生产者可能拿到锁,或者只有一个消费者能拿到锁。
(2)wait拿到锁后,生产者可能产生了不止一个任务,因为生产者也一直在参与锁的竞争,可能连续拿好几次。
(3)unique_lock可以提前解锁,但是如果在解锁后执行一个稍微耗时的操作,则此时生产者已经拿到锁,生产任务后执行了唤醒操作,但消费者没有卡在wait处,则此次唤醒失败。即notify_one不是每次都能唤醒没有处在睡眠状态的wait。
(4)生产者产生任务速度大于消费者消费速度,导致很多任务被积压
三 notify_all()
notify_one()只能通知一个线程,使其被唤醒。
notify_all :将所有的线程中的wait都被唤醒。
虽然是将所有处于睡眠的wait()唤醒,但还是只能由一个线程拿到互斥锁,所以看起来的效果与notify_one相同,但可以用于不同的工作场景。