1. 程式人生 > 實用技巧 >C++11多執行緒程式設計(五)——生產消費者模型之條件變數

C++11多執行緒程式設計(五)——生產消費者模型之條件變數

當某個執行緒持有這把鎖的時候(就是所謂的加鎖),那麼這個執行緒是獨佔所有的資源,這裡的資源指的是執行的許可權,其他要搶奪資源的執行緒都不得不等待。在很多情況下,這都容易適用,但是有些情況下,卻會產生一些異常情況。

在生產消費者模型當中,肯定都會用到互斥鎖的機制的,當生產者往佇列中放資料的瞬間,消費者是不能取資料的,那這時候可能會碰見一個問題,如果生成者因為某些原因,放資料過慢,但是消費者取資料很快,當佇列中沒有資料了,消費者還去取的話,就會發生異常情況。有些人可能會說,加個條件判斷一下佇列是否為空不就可以了。

這個肯定是當然可以的,但是在佇列依舊沒有資料的這一段時間,是要不斷的迴圈判斷這個條件,CPU肯定是會飆升的,浪費了很多不必要的資源。

這時候我們設想,能否設計這樣的一種機制,如果在佇列沒有資料的時候,消費者執行緒能一直阻塞在那裡,等待著別人給它喚醒,在生產者往佇列中放入資料的時候通知一下這個等待執行緒,喚醒它,告訴它可以來取資料了。

於是多執行緒中的條件變數就橫空出世!

條件變數是多執行緒資料同步的一種操作,不管是用哪種框架,哪種語言實現多執行緒的功能,條件變數都是不得不考慮的一種情況。C++中提供了#include <condition_variable>標頭檔案,裡面就包含了條件變數的相關類。其中有兩個非常重要的介面,wait()和notify_one(),wait()可以讓執行緒陷入休眠狀態,意思就是不幹活了,notify_one()就是喚醒真正休眠狀態的執行緒,開始幹活了。當然還有notify_all()這個介面,顧名思義,就是通知所有正在等待的執行緒,起來幹活了。

以下是程式碼的實現部分

#include <iostream>
#include <deque>
#include <thread>
#include <mutex>
#include <condition_variable>
using namespace std;
 
deque<int> q;
mutex mt;
condition_variable cond;
 
void thread_producer()
{
  int count = 10;
  while (count > 0)
  {
    unique_lock
<mutex> unique(mt); q.push_front(count); unique.unlock(); cout << "producer a value: " << count << endl; cond.notify_one(); this_thread::sleep_for(chrono::seconds(1)); count--; } } void thread_consumer() { int data = 0; while (data != 1) { unique_lock<mutex> unique(mt); while (q.empty()) cond.wait(unique); data = q.back(); q.pop_back(); cout << "consumer a value: " << data << endl; unique.unlock(); } } int main() { thread t1(thread_consumer); thread t2(thread_producer); t1.join(); t2.join(); return 0; }

生產者:首先生產者利用unique_lock來加鎖,然後將生產的資料放入佇列,列印,解鎖,一旦解鎖之後,消費者獲得了執行機會。

消費者:另一方面消費者就會通過unique_lock獲得控制權,也就是獲得鎖,然後判斷佇列為空的話就一直盜用wait()函式阻塞在那裡,等待其他執行緒來喚醒它。而阻塞該執行緒時,該函式會自動解鎖,允許其他執行緒執行。

生產者:再次回到生產者這裡,生產者執行緒利用利用條件變數cond.notify_one()來通知阻塞的執行緒起來幹活了。

消費者:阻塞在那裡的消費者執行緒一旦得到notify喚醒,該函式取消阻塞並獲取鎖,然後取出佇列中的資料,並列印,最後解鎖。

生產者:再次回到生產者,然後生產者休眠1秒,這裡休眠是為了模擬生產者生產慢的情況,實際開發的時候不要去休眠。最後減一,進入下一次生產。

以上就是利用條件變數來實現生產消費者模型,這個會大大降低CPU的佔有率,當然代價就是程式設計稍微有點麻煩,但與這優化程式來比,這肯定是值的。

更多精彩內容,請關注同名公眾:一點筆記alittle