多線程 一個線程等待某種事件發生
背景:某個線程在能夠完成其任務之前可能需要等待另一個線程完成其任務。
例如:坐夜間列車,為了能夠不坐過站,
1,整夜保持清醒,但是這樣你就會非常累,不能夠睡覺。
2,如果你知道幾點會到你要下車的站,就可以提前定個鬧鍾,然后睡覺等待鬧鍾叫醒你,但是如果車中間有延誤,鬧鍾響了,但是還沒到你要下次的站;還有一種更惡劣的情況就是,鬧鍾還沒響,但是列車已經過站了。
3,最好的辦法就是,快到站前,有個人能把你叫醒。
為了能夠達到上面場景3的效果,條件變量(Condition variable)就登場了。
對應上面的3個場景,請看下面的代碼。
場景1的代碼:
while(某個條件){//這個條件由另一個線程來變更,所以就一直循環來檢查這個條件,CPU就得不到休息,浪費系統的性能
}
場景2的代碼:
std::unique_lock<std::mutex> lk(m);
while(某個條件){//這個條件由另一個線程來變更,先睡眠一會,等待別的線程變更這個條件,CPU得到了休息,節省了系統的性能
lk.unlock();
sleep(休眠一定的時間);
lk.lock();
}
//缺點:無法准確知道要休眠多長的時間。休眠時間過長就會導致響應過慢,休眠時間過短,醒來發現條件還沒被變更,還得繼續休眠。
場景3的代碼:
#include <iostream>
#include <mutex>
#include <queue>
#include <condition_variable>
#include <thread>
#include <unistd.h>//sleep
std::mutex mut;
std::queue<int> data_queue;//-------------------①
std::condition_variable data_cond;
void data_preparation_thread(){
int data = 0;
while(true){
data++;
std::lock_guard<std::mutex> lk(mut);
data_queue.push(data);//-------------------②
data_cond.notify_one();//-------------------③
std::cout << "after notify_one" << std::endl;
//std::this_thread::sleep_for(1000);
sleep(1);
}
}
void data_process_thread(){
while(true){
std::unique_lock<std::mutex> lk(mut);//-------------------④
std::cout << "before wait" << std::endl;
data_cond.wait(lk, []{return !data_queue.empty();});//-------------------⑤
std::cout << "after wait" << std::endl;
int data = data_queue.front();
std::cout << data << std::endl;
data_queue.pop();
lk.unlock();//-------------------⑥
//假設處理數據data的函數process要花費大量時間,所以提前解鎖
//process(data);
}
}
int main(){
std::thread t1(data_preparation_thread);
std::thread t2(data_process_thread);
t1.join();
t2.join();
}
1,有一個在多個線程間傳遞數據的隊列①,修改隊列前鎖定隊列,把數據壓入隊列②,壓入完成后通知等待它的線程,說:我已經把數據做好,你們可以使用了③。
2,另一個線程使用隊列前,先鎖定這個隊列④,注意是用std::unique_lock而不是std::lock_guard,理由后面說。
3,data_cond.wait(),檢查隊列里是否有數據(用的是lambda函數,也可以是普通函數),
- 如果條件不滿足(lambda函數返回false),wait解鎖這個互斥元,並將該線程置於阻塞狀態,繼續等待notify_onde()來喚醒它。
- 如果條件滿足(lambda函數返回true),wait繼續鎖定這個互斥元,執行wait后面的代碼。
這就是為什么使用std::unique_lock而不是std::lock_guard。等待中的線程必須解鎖互斥元,並在wait返回true的時候重新鎖定這個互斥元,std::lock_guard沒有這個功能。如果線程在等待期間不解鎖互斥元,把數據壓入隊列的線程就無法鎖定這個互斥元,就無法壓入數據,就無法執行notify_one(),所以等待的線程就永遠處於等待狀態。。。
4,std::unique_lock另外的靈活性,假設得到隊列里的數據后,要做一個特別耗時的處理,做這個耗時的處理前就應該解鎖這個互斥元⑥,std::unique_lock提供了這個靈活性,而std::lock_guard沒有提供這個靈活性。
5,notify_one()后,另一個wait的線程不是馬上就被喚醒!!!
編譯方法:
g++ -g condition_vari-4.1.cpp -std=c++11 -pthread