条件变量
这一章主要讲讲条件变量condition_variable。条件变量是一个非常神奇的存在,是线程间交互的一种方式。
C++11提供了condition_variable类。使用时需要include头文件<condition_variable>。
如果把变量区看成是一座房子,那么前面两章频繁用到的mutex可以看成是房门的锁,正常来说是房门常年打开的,锁并用不上。但是有了多线程以后,为了防止多个线程一窝蜂胡乱篡改里面的数据,所以就有了锁的概念。
现在假设每个线程都有一个管理锁的人,叫lock_guard,或者unique_lock,但是一次只能有一个人能够去操作锁(锁上或者是解锁)。一般来说他们是轮流去操作锁。而condition_variable则可以看做是门童,如果没有满足条件,门童就会通知线程的管锁人必须要休眠而不可以操作锁,可是一旦条件满足,他就会唤醒某些线程的管锁人可以去操作锁了。
#include<thread> #include<iostream> #include<mutex> #include<string> #include<condition_variable> using namespace std; bool ready = false; bool processed = false; mutex mu; condition_variable cv; string data; void worker_thread() { unique_lock<mutex> locker(mu); //ready = false,此处相当于全局变量区的门童通知t线程休眠 cv.wait(locker, [](){return ready;}); //ready = true,休眠结束。此时locker上锁,开始修改变量 cout<<"start processing data"<<endl; data += " after processing"; processed = true; cout<<"worker thread has finished processing data"<<endl; //门童通知其他线程全局变量的最新情况 //注意这一步是非常重要的,可以立即唤醒其他线程,否则其他线程会一直等待,这个过程可能会十分耗费时间 //这也是为什么要用条件变量的原因 cv.notify_one(); //locker解锁 } int main() { thread t(worker_thread); //启动t线程 data = "example data"; unique_lock<mutex> locker(mu); //locker开始上锁,main线程修改全局变量 ready = true; cout<<"main signals data ready for processing"<<endl; //门童通知其他线程全局变量的最新情况 cv.notify_one(); //locker解锁 //processed = false, 门童通知main线程休眠 cv.wait(locker, [](){return processed;}); //processed = true, locker上锁 cout<<"back in main, data = "<<data<<endl; //locker解锁 t.join(); }
上面的代码中需要注意一下几点:
1. 代码中的 [](){return ready;}是匿名函数,也可以用循环的写法。
//cv.wait(locker, [](){return ready;}); //或者写成 while(ready==false) cv.wait(locker);
注意是while(ready == false),不是if(ready == false),因为wait的唤醒可能由于系统的原因被唤醒,这个的时机是不确定的。这个过程也被称作伪唤醒(spurious wakeup)。
如果在错误的时候被唤醒,就开始执行了后面的操作就会造成错误。
2. 注意cv.wait() 和cv.notify_all()或者cv.notify_one()需要搭配使用才能真正发挥条件变量的作用。
3. cv.notify_one()指的是通知其中某一个线程,cv.notify_all()指的是通知全部线程。
参考:
https://www.jianshu.com/p/c1dfa1d40f53