C++ 條件變數(condition_variable)

  • 2020 年 2 月 15 日
  • 筆記

       先貼一個condition_variable的講解:https://en.cppreference.com/w/cpp/thread/condition_variable,很詳細也很全面,但是是英文的,勸退了一部分英語不好的人(也包括我),但是藉助翻譯還是大概可以看下來的,而且裡面的兩個程式碼也很有代表性,使用的生產者消費者模式,推給大家。

       condition_variable是一個類,搭配互斥量mutex來用,這個類有它自己的一些函數,這裡就主要講wait函數和notify_*函數,故名思意,wait就是有一個等待的作用,notify就是有一個通知的作用。主要用法這裡就不再贅述了,簡而言之就是程式運行到wait函數的時候會先在此阻塞,然後自動unlock,那麼其他執行緒在拿到鎖以後就會往下運行,當運行到notify_one()函數的時候,就會喚醒wait函數,然後自動lock並繼續下運行。

       當然wait還有第二個參數,這個參數接收一個布爾類型的值,當這個布爾類型的值為false的時候執行緒就會被阻塞在這裡,只有當該執行緒被喚醒之後,且第二參數為true才會往下運行。

       notify_one()每次只能喚醒一個執行緒,那麼notify_all()函數的作用就是可以喚醒所有的執行緒,但是最終能搶奪鎖的只有一個執行緒,或者說有多個執行緒在wait,但是用notify_one()去喚醒其中一個執行緒,那麼這些執行緒就出現了去爭奪互斥量的一個情況,那麼最終沒有獲得鎖的控制權的執行緒就會再次回到阻塞的狀態,那麼對於這些沒有搶到控制權的這個過程就叫做虛假喚醒。那麼對於虛假喚醒的解決方法就是加一個while循環,比如下面這樣:

while (que.size() == 0) {  	cr.wait(lck);  }

       這個就是當執行緒被喚醒以後,先進行判斷,是否可以去操作,如果可以再去運行下面的程式碼,否則繼續在循環內執行wait函數。

       補充一個小的知識點,上面所說的多個執行緒等待一個喚醒的情況叫做驚群效應(了解的不多,大家可以自己查一下)。

       下面就貼一個生產者消費者模式的程式碼:

#include <iostream>  #include <thread>  #include <mutex>  #include <queue>  #include <windows.h>  #include <condition_variable>    std::mutex mtx;        // 全局互斥鎖  std::queue<int> que;   // 全局消息隊列  std::condition_variable cr;   // 全局條件變數  int cnt = 1;           // 數據    void producer() {  	while(true) {  		{  			std::unique_lock<std::mutex> lck(mtx);  			// 在這裡也可以加上wait 防止隊列堆積  while(que.size() >= MaxSize) que.wait();  			que.push(cnt);  			std::cout << "向隊列中添加數據:" << cnt ++ << std::endl;  			// 這裡用大括弧括起來了 為了避免出現虛假喚醒的情況 所以先unlock 再去喚醒  		}  		cr.notify_all();       // 喚醒所有wait  	}  }    void consumer() {  	while (true) {  		std::unique_lock<std::mutex> lck(mtx);  		while (que.size() == 0) {           // 這裡防止出現虛假喚醒  所以在喚醒後再判斷一次  			cr.wait(lck);  		}  		int tmp = que.front();  		std::cout << "從隊列中取出數據:" << tmp << std::endl;  		que.pop();  	}  }    int main()  {  	std::thread thd1[2], thd2[2];  	for (int i = 0; i < 2; i++) {  		thd1[i] = std::thread(producer);  		thd2[i] = std::thread(consumer);  		thd1[i].join();  		thd2[i].join();  	}  	return 0;  }