C 条件变量使用详解
condition_variable介紹
在C 11中,我們可以使用條件變量(condition_variable)實現多個線程間的同步操作;當條件不滿足時,相關線程被一直阻塞,直到某種條件出現,這些線程才會被喚醒。
其主要成員函數如下:
條件變量是利用線程間共享的全局變量進行同步的一種機制,主要包括兩個動作:
一個線程因等待"條件變量的條件成立"而掛起;
另外一個線程使"條件成立",給出信號,從而喚醒被等待的線程。
為了防止競爭,條件變量的使用總是和一個互斥鎖結合在一起;通常情況下這個鎖是std::mutex,并且管理這個鎖 只能是 std::unique_lockstd::mutex RAII模板類。
上面提到的兩個步驟,分別是使用以下兩個方法實現:
等待條件成立使用的是condition_variable類成員wait 、wait_for 或 wait_until。
給出信號使用的是condition_variable類成員notify_one或者notify_all函數。
細節說明
在條件變量中只能使用std::unique_lock< std::mutex >說明
unique_lock和lock_guard都是管理鎖的輔助類工具,都是RAII風格;它們是在定義時獲得鎖,在析構時釋放鎖。它們的主要區別在于unique_lock鎖機制更加靈活,可以再需要的時候進行lock或者unlock調用,不非得是析構或者構造時。它們的區別可以通過成員函數就可以一目了然。在這里插入圖片描述
wait/wait_for說明
線程的阻塞是通過成員函數wait()/wait_for()/wait_until()函數實現的。這里主要說明前面兩個函數:
wait()成員函數
函數聲明如下:
void?wait(?std::unique_lock<std::mutex>&?lock?); //Predicate?謂詞函數,可以普通函數或者lambda表達式 template<?class?Predicate?> void?wait(?std::unique_lock<std::mutex>&?lock,?Predicate?pred?);wait 導致當前線程阻塞直至條件變量被通知,或虛假喚醒發生,可選地循環直至滿足某謂詞。
wait_for()成員函數
函數聲明如下:
template<?class?Rep,?class?Period?> std::cv_status?wait_for(?std::unique_lock<std::mutex>&?lock,const?std::chrono::duration&?rel_time);template<?class?Rep,?class?Period,?class?Predicate?> bool?wait_for(?std::unique_lock<std::mutex>&?lock,const?std::chrono::duration&?rel_time,Predicate?pred);wait_for 導致當前線程阻塞直至條件變量被通知,或虛假喚醒發生,或者超時返回。
返回值說明:
若經過 rel_time 所指定的關聯時限則為 std::cv_status::timeout ,否則為 std::cv_status::no_timeout 。
若經過 rel_time 時限后謂詞 pred 仍求值為 false 則為 false ,否則為 true 。
以上兩個類型的wait函數都在會阻塞時,自動釋放鎖權限,即調用unique_lock的成員函數unlock(),以便其他線程能有機會獲得鎖。這就是條件變量只能和unique_lock一起使用的原因,否則當前線程一直占有鎖,線程被阻塞。
notify_all/notify_one
notify函數聲明如下:
void?notify_one()?noexcept;若任何線程在 *this 上等待,則調用 notify_one 會解阻塞(喚醒)等待線程之一。
void?notify_all()?noexcept;若任何線程在 *this 上等待,則解阻塞(喚醒)全部等待線程。
虛假喚醒
在正常情況下,wait類型函數返回時要不是因為被喚醒,要不是因為超時才返回,但是在實際中發現,因此操作系統的原因,wait類型在不滿足條件時,它也會返回,這就導致了虛假喚醒。因此,我們一般都是使用帶有謂詞參數的wait函數,因為這種(xxx, Predicate pred )類型的函數等價于:
while?(!pred())?//while循環,解決了虛假喚醒的問題 {wait(lock); }原因說明如下:
假設系統不存在虛假喚醒的時,代碼形式如下:
if?(不滿足xxx條件) {//沒有虛假喚醒,wait函數可以一直等待,直到被喚醒或者超時,沒有問題。//但實際中卻存在虛假喚醒,導致假設不成立,wait不會繼續等待,跳出if語句,//提前執行其他代碼,流程異常wait();?? }//其他代碼 ...正確的使用方式,使用while語句解決:
while?(!(xxx條件)?) {//虛假喚醒發生,由于while循環,再次檢查條件是否滿足,//否則繼續等待,解決虛假喚醒wait();?? } //其他代碼 ....條件變量使用
在這里,我們使用條件變量,解決生產者-消費者問題,該問題主要描述如下:
生產者-消費者問題,也稱有限緩沖問題,是一個多進程/線程同步問題的經典案例。該問題描述了共享固定大小緩沖區的兩個進程/線程——即所謂的“生產者”和“消費者”,在實際運行時會發生的問題。
生產者的主要作用是生成一定量的數據放到緩沖區中,然后重復此過程。與此同時,費者也在緩沖區消耗這些數據。該問題的關鍵就是要保證生產者不會在緩沖區滿時加入數據,消費者也不會在緩沖區中空時消耗數據。
要解決該問題,就必須讓生產者在緩沖區滿時休眠(要么干脆就放棄數據),等到下次消費者消耗緩沖區中的數據的時候,生產者才能被喚醒,開始往緩沖區添加數據。
同樣,也可以讓消費者在緩沖區空時進入休眠,等到生產者往緩沖區添加數據之后,再喚醒消費者。
生產者-消費者代碼如下:
std::mutex?g_cvMutex; std::condition_variable?g_cv;//緩存區 std::deque<int>?g_data_deque; //緩存區最大數目 const?int??MAX_NUM?=?30; //數據 int?g_next_index?=?0;//生產者,消費者線程個數 const?int?PRODUCER_THREAD_NUM??=?3; const?int?CONSUMER_THREAD_NUM?=?3;void??producer_thread(int?thread_id) {while?(true){std::this_thread::sleep_for(std::chrono::milliseconds(500));//加鎖std::unique_lock?<std::mutex>?lk(g_cvMutex);//當隊列未滿時,繼續添加數據g_cv.wait(lk,?[](){?return?g_data_deque.size()?<=?MAX_NUM;?});g_next_index ;g_data_deque.push_back(g_next_index);std::cout?<<?"producer_thread:?"?<<?thread_id?<<?"?producer?data:?"?<<?g_next_index;std::cout?<<?"?queue?size:?"?<<?g_data_deque.size()?<<?std::endl;//喚醒其他線程?g_cv.notify_all();//自動釋放鎖} }void??consumer_thread(int?thread_id) {while?(true){std::this_thread::sleep_for(std::chrono::milliseconds(550));//加鎖std::unique_lock?<std::mutex>?lk(g_cvMutex);//檢測條件是否達成g_cv.wait(?lk,???[]{?return?!g_data_deque.empty();?});//互斥操作,消息數據int?data?=?g_data_deque.front();g_data_deque.pop_front();std::cout?<<?"\tconsumer_thread:?"?<<?thread_id?<<?"?consumer?data:?";std::cout?<<?data?<<?"?deque?size:?"?<<?g_data_deque.size()?<<?std::endl;//喚醒其他線程g_cv.notify_all();//自動釋放鎖} }int?main()總結
以上是生活随笔為你收集整理的C 条件变量使用详解的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: linux删除非空目录的命令(linux
- 下一篇: 多线程队列的算法优化