有以下场景:线程A须要等线程B完成操做后,再进行执行;在线程B完成操做前,线程A出于睡眠状态,当线程B完成操做后,唤醒线程A。这里,注意,在线程B完成前,A是处于睡眠状态,即此时,A不占用CPU,不可用原子变量+while死循环来等待(while(!done)),这样的话CPU会处于一直运行状态。ios
可使用sleep来处理,但因为A不知道须要sleep多长时间,因此,sleep不合适。这里使用条件变量,来处理该问题。函数
条件变量:当收到notify通知时,检查是否知足某个条件,知足时,线程中止wait,被唤醒,从新加锁,继续执行。this
总体流程以下图,具体见代码注释atom
#include<iostream> #include<thread> #include<mutex> #include<condition_variable> using namespace std; // 使用条件变量的三剑客: /* 1.互斥元 2.条件变量 3.条件值done */ mutex mA; condition_variable cv; bool done = false; void f() { unique_lock<mutex> _1(mA); // 条件变量的wait所必须是unique_lock而不是lock_guard,由于wait会在内部调用unique_lock.unlock先解锁,当被唤醒后,条件知足时,会unique_lock.lock // 条件为:当done为true时,收到notify的线程会被唤醒,不然即便收到notify,也不会被唤醒 cv.wait(_1, [] {return done; }); cout << "has done" << endl; // 须要手动释放锁 _1.unlock(); } void f2() { // 这里使用lock_guard在mA上加锁便可 lock_guard<mutex> _1(mA); cout << "f2" << endl; std::this_thread::sleep_for(1s); //必须将条件done设置为true,不然线程t1不会被唤醒 done = true; //通知一个线程,让收到的线程检查其条件,收到通知的线程发现条件知足,则该线程会被唤醒 cv.notify_one(); } int main(int argc, int * argv[]) { thread t1(f); thread t2(f2); t1.join(); t2.join(); cout << "main" << endl; system("pause"); }
结果以下:spa
注意,若是在f2中,忘记将done 设置为 true,则f1不会被唤醒,会一直等待:线程
void f2() { // 这里使用lock_guard在mA上加锁便可 lock_guard<mutex> _1(mA); cout << "f2" << endl; std::this_thread::sleep_for(1s); //必须将条件done设置为true,不然线程t1不会被唤醒 //done = true; //通知一个线程,让收到的线程检查其条件,收到通知的线程发现条件知足,则该线程会被唤醒 cv.notify_one(); }
结果以下:code
因此:条件变量的本质在于,只有当条件被知足时,线程才会被唤醒,而不是收到notify了,该等待线程就会被唤醒。源码
条件变量实际上实现了线程间的数据共享操做。线程A在修改完某些数据后,经过条件变量,来通知线程B来获取最新的数据修改值。it
代码能够以下:io
#include<iostream> #include<thread> #include<mutex> #include<condition_variable> using namespace std; // 使用条件变量的三剑客: /* 1.互斥元 2.条件变量 3.条件值done */ mutex mA; condition_variable cv; bool done = false; int age = 0; void f() { unique_lock<mutex> _1(mA); // 条件变量的wait所必须是unique_lock而不是lock_guard,由于wait会在内部调用unique_lock.unlock先解锁,当被唤醒后,条件知足时,会unique_lock.lock // 条件为:当done为true时,收到notify的线程会被唤醒,不然即便收到notify,也不会被唤醒 cv.wait(_1, [] {return done; }); cout << "has done" << endl; cout << "age=" << age << endl; // 须要手动释放锁 _1.unlock(); } void f2() { // 这里使用lock_guard在mA上加锁便可 lock_guard<mutex> _1(mA); cout << "f2" << endl; std::this_thread::sleep_for(1s); age = 1000; //必须将条件done设置为true,不然线程t1不会被唤醒 done = true; //通知一个线程,让收到的线程检查其条件,收到通知的线程发现条件知足,则该线程会被唤醒 cv.notify_one(); } int main(int argc, int * argv[]) { thread t1(f); thread t2(f2); t1.join(); t2.join(); cout << "main" << endl; system("pause"); }
age做为两个线程的共享变量,一个修改,一个使用修改后的值。
结果以下:
【坑】
在利用条件变量进行线程间等待与通知时,wait能够不用传递谓词,即判断条件函数。
这时,会有一个问题。若是线程A先notify,而线程B后wait,则线程B永远不会被唤醒。
#include<iostream> #include<thread> #include<mutex> #include<condition_variable> #include<future> #include<chrono> using namespace std; mutex mA; condition_variable cv; void f() { lock_guard<mutex> lock(mA); std::this_thread::sleep_for(1s); cv.notify_one(); cout << "notify" << endl; } void f2() { unique_lock<mutex> lock(mA); cout << "wait" << endl; // wait没有传入第二个参数 cv.wait(lock); cout << "wake" << endl; lock.unlock(); } int main(int argc, int * argv[]) { thread t1(f); // 先notify thread t2(f2); // 后wait,一直等待,不会被唤醒 t1.join(); t2.join(); cout << "main" << endl; system("pause"); }
结果以下:一直在wait,唤醒失败。
其实,这是一个先通知后等待的问题,若是先等待,后通知,则没有问题。
#include<iostream> #include<thread> #include<mutex> #include<condition_variable> #include<future> #include<chrono> using namespace std; mutex mA; condition_variable cv; void f() { lock_guard<mutex> lock(mA); std::this_thread::sleep_for(1s); cv.notify_one(); cout << "notify" << endl; } void f2() { unique_lock<mutex> lock(mA); cout << "wait" << endl; cv.wait(lock); cout << "wake" << endl; lock.unlock(); } int main(int argc, int * argv[]) { thread t2(f2); // 先wait, thread t1(f); // 后notify t1.join(); t2.join(); cout << "main" << endl; system("pause"); }
结果以下:线程正常被唤醒
如何解决这个问题呢?
方法一:给wait条件第二个参数,即谓词条件。
方法二:不给wait添加第二个参数,可是使用done标志位。当notify以后,该标志位设置为true。wait以前先判断该标志位。若是先通知,则该标志位为true,不会调用cv.wait,也就不会进入等待
#include<iostream> #include<thread> #include<mutex> #include<condition_variable> #include<future> #include<chrono> #include<atomic> using namespace std; mutex mA; condition_variable cv; atomic<bool> done(false); void f() { lock_guard<mutex> lock(mA); std::this_thread::sleep_for(1s); cv.notify_one(); done = true; cout << "notify" << endl; } void f2() { unique_lock<mutex> lock(mA); while (!done) { cout << "wait" << endl; cv.wait(lock); } cout << "wake" << endl; lock.unlock(); } int main(int argc, int * argv[]) { thread t1(f); // 先notify thread t2(f2); // 后wait, t1.join(); t2.join(); cout << "main" << endl; system("pause"); }
结果以下:
其实,从本质上来讲,方法一与方法二是相同的,看看vs2017里面的源码
因此,为了不这种唤醒是失败的问题(失败的缘由在于先notify后wait),最好使用第一种方法,这种作法简单,方便。逻辑上比第二种作法清晰。