一、總述
在C++11中,我們可以使用條件變量(condition_variable)實現多個線程間的同步操作;當條件不滿足時,相關線程被一直阻塞,直到某種條件出現,這些線程才會被喚醒。
主要成員函數如下:
二、具體函數:
1、wait函數:
(1)wait(unique_lock <mutex>&lck)
當前線程的執行會被阻塞,直到收到 notify 為止。
(2)wait(unique_lock <mutex>&lck,Predicate pred)
當前線程僅在pred=false時阻塞;如果pred=true時,不阻塞。
wait()可依次拆分為三個操作:釋放互斥鎖、等待在條件變量上、再次獲取互斥鎖
2、notify_one:
notify_one():沒有參數、沒有返回值。
解除阻塞當前正在等待此條件的線程之一。如果沒有線程在等待,則還函數不執行任何操作。如果超過一個,不會指定具體哪一線程。
// condition_variable::notify_one #include <iostream> // std::cout #include <thread> // std::thread #include <mutex> // std::mutex, std::unique_lock #include <condition_variable> // std::condition_variable std::mutex mtx; std::condition_variable produce,consume; int cargo = 0; // shared value by producers and consumers void consumer () { std::unique_lock<std::mutex> lck(mtx); while (cargo==0) consume.wait(lck); std::cout << cargo << '\n'; cargo=0; produce.notify_one(); } void producer (int id) { std::unique_lock<std::mutex> lck(mtx); while (cargo!=0) produce.wait(lck); cargo = id; consume.notify_one(); } int main () { std::thread consumers[10],producers[10]; // spawn 10 consumers and 10 producers: for (int i=0; i<10; ++i) { consumers[i] = std::thread(consumer); producers[i] = std::thread(producer,i+1); } // join them back: for (int i=0; i<10; ++i) { producers[i].join(); consumers[i].join(); } return 0; }
三、分析
條件變量是利用線程間共享的全局變量進行同步的一種機制,主要包括兩個動作:
(1)、一個線程因等待“條件變量的條件成立”而掛起;
(2)、另外一個線程使“條件成立”,給出信號,從而喚醒被等待的線程。
1、有什么用:
當需要死循環判斷某個條件成立與否時【true or false】,我們往往需要開一個線程死循環來判斷,這樣非常消耗CPU。使用條件變量,可以讓當前線程wait,釋放CPU,如果條件改變時,我們再notify退出線程,再次進行判斷。
2、其他解釋
想要修改共享變量(即“條件”)的線程必須:
(1). 獲得一個std::mutex
(2). 當持有鎖的時候,執行修改動作
(3). 對std::condition_variable執行notify_one或notify_all(當做notify動作時,不必持有鎖)
即使共享變量是原子性的,它也必須在mutex的保護下被修改,這是為了能夠將改動正確發布到正在等待的線程。
任意要等待std::condition_variable的線程必須:
(1). 獲取std::unique_lock<std::mutex>,這個mutex正是用來保護共享變量(即“條件”)的
(2). 執行wait, wait_for或者wait_until. 這些等待動作原子性地釋放mutex,並使得線程的執行暫停
(3). 當獲得條件變量的通知,或者超時,或者一個虛假的喚醒,那么線程就會被喚醒,並且獲得mutex. 然后線程應該檢查條件是否成立,如果是虛假喚醒,就繼續等待。
【注: 所謂虛假喚醒,就是因為某種未知的罕見的原因,線程被從等待狀態喚醒了,但其實共享變量(即條件)並未變為true。因此此時應繼續等待】
std::deque<int> q; std::mutex mu; std::condition_variable cond; void function_1() //生產者 { int count = 10; while (count > 0) { std::unique_lock<std::mutex> locker(mu); q.push_front(count); locker.unlock(); cond.notify_one(); // Notify one waiting thread, if there is one. std::this_thread::sleep_for(std::chrono::seconds(1)); count--; } } void function_2() //消費者 { int data = 0; while (data != 1) { std::unique_lock<std::mutex> locker(mu); while (q.empty()) cond.wait(locker); // Unlock mu and wait to be notified data = q.back(); q.pop_back(); locker.unlock(); std::cout << "t2 got a value from t1: " << data << std::endl; } } int main() { std::thread t1(function_1); std::thread t2(function_2); t1.join(); t2.join(); return 0; }
核心:
①、在消費者里判斷隊列是否為空后,如果不為空則wait,等待生產者發送notify信號
②、在生產者那里,如果生產了任務,則發送notify信號,告訴消費者可以試圖退出wait,判斷隊列是否為空,如果有任務則調度處理任務,如果還是空則說明此次notify是錯誤的,可能是其他地方發出來干擾的,生產者繼續wait。
③、流程:
軟件開啟,生成消費者線程消費隊列,應該是一個while循環,在循環里獲取鎖,再來一個while循環判斷條件,如果條件不成立則wait,wait會自動釋放鎖;
此時消費者已經沒有鎖了,在生產者線程里,獲取鎖,然后往里面加任務,退出作用域釋放鎖,然后notify告知消費者退出wait,消費者重新獲取鎖,然后從隊列里取任務;
整個過程,生產者加任務時生產者持有鎖,消費者取任務時消費者持有鎖。
資料:
https://www.cnblogs.com/judes/p/11132918.html
https://www.cnblogs.com/judes/p/11230628.html