有以下場景:線程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),最好使用第一種方法,這種作法簡單,方便。邏輯上比第二種作法清晰。