std::condition_variable 詳解

<condition_variable > 頭文件主要包含了與條件變量相關(guān)的類(lèi)和函數(shù)。相關(guān)的類(lèi)包括 std::condition_variable 和 std::condition_variable_any,還有枚舉類(lèi)型std::cv_status。另外還包括函數(shù) std::notify_all_at_thread_exit(),下面分別介紹一下以上幾種類(lèi)型。
std::condition_variable 類(lèi)介紹
std::condition_variable 是條件變量,更多有關(guān)條件變量的定義參考維基百科Linux 下使用 Pthread 庫(kù)中的 pthread_cond_*() 函數(shù)提供了與條件變量相關(guān)的功能, Windows 則參考 MSDN
當(dāng) std::condition_variable 對(duì)象的某個(gè) wait 函數(shù)被調(diào)用的時(shí)候,它使用 std::unique_lock(通過(guò) std::mutex) 來(lái)鎖住當(dāng)前線程。當(dāng)前線程會(huì)一直被阻塞,直到另外一個(gè)線程在相同的 std::condition_variable 對(duì)象上調(diào)用了 notification 函數(shù)來(lái)喚醒當(dāng)前線程。
std::condition_variable 對(duì)象通常使用 std::unique_lock<std::mutex> 來(lái)等待,如果需要使用另外的 lockable 類(lèi)型,可以使用 std::condition_variable_any 類(lèi),本文后面會(huì)講到 std::condition_variable_any 的用法。

首先我們來(lái)看一個(gè)簡(jiǎn)單的[例子]



#include <iostream> // std::cout#include <thread> // std::thread#include <mutex> // std::mutex, std::unique_lock#include <condition_variable> // std::condition_variablestd::mutex mtx; // 全局互斥鎖.std::condition_variable cv; // 全局條件變量.bool ready = false; // 全局標(biāo)志位.void do_print_id(int id){ std::unique_lock <std::mutex> lck(mtx); while (!ready) // 如果標(biāo)志位不為 true, 則等待... cv.wait(lck); // 當(dāng)前線程被阻塞, 當(dāng)全局標(biāo)志位變?yōu)?true 之后, // 線程被喚醒, 繼續(xù)往下執(zhí)行打印線程編號(hào)id. std::cout << "thread " << id << '\n';}void go(){ std::unique_lock <std::mutex> lck(mtx); ready = true; // 設(shè)置全局標(biāo)志位為 true. cv.notify_all(); // 喚醒所有線程.}int main(){ std::thread threads[10]; // spawn 10 threads: for (int i = 0; i < 10; ++i) threads[i] = std::thread(do_print_id, i); std::cout << "10 threads ready to race...\n"; go(); // go! for (auto & th:threads) th.join(); return 0;}

執(zhí)行結(jié)果如下:


concurrency ) ./ConditionVariable-basic1 10 threads ready to race...thread 1thread 0thread 2thread 3thread 4thread 5thread 6thread 7thread 8thread 9

好了,對(duì)條件變量有了一個(gè)基本的了解之后,我們來(lái)看看 std::condition_variable 的各個(gè)成員函數(shù)。
std::condition_variable 構(gòu)造函數(shù)
default (1)
condition_variable();

copy [deleted] (2)
condition_variable (const condition_variable&) = delete;

std::condition_variable 的拷貝構(gòu)造函數(shù)被禁用,只提供了默認(rèn)構(gòu)造函數(shù)。
std::condition_variable::wait() 介紹
unconditional (1)
void wait (unique_lock<mutex>& lck);

predicate (2)
template <class Predicate> void wait (unique_lock<mutex>& lck, Predicate pred);

std::condition_variable 提供了兩種 wait() 函數(shù)。當(dāng)前線程調(diào)用 wait() 后將被阻塞(此時(shí)當(dāng)前線程應(yīng)該獲得了鎖(mutex),不妨設(shè)獲得鎖 lck),直到另外某個(gè)線程調(diào)用 notify_* 喚醒了當(dāng)前線程。
在線程被阻塞時(shí),該函數(shù)會(huì)自動(dòng)調(diào)用 lck.unlock() 釋放鎖,使得其他被阻塞在鎖競(jìng)爭(zhēng)上的線程得以繼續(xù)執(zhí)行。
另外,一旦當(dāng)前線程獲得通知(notified,通常是另外某個(gè)線程調(diào)用 notify_* 喚醒了當(dāng)前線程),wait() 函數(shù)也是自動(dòng)調(diào)用 lck.lock(),使得 lck 的狀態(tài)和 wait 函數(shù)被調(diào)用時(shí)相同。在第二種情況下(即設(shè)置了 Predicate),只有當(dāng) pred 條件為 false 時(shí)調(diào)用 wait() 才會(huì)阻塞當(dāng)前線程,并且在收到其他線程的通知后只有當(dāng) pred 為 true 時(shí)才會(huì)被解除阻塞。因此第二種情況類(lèi)似以下代碼:
while (!pred()) wait(lck);

請(qǐng)看下面例子(參考):


#include <iostream> // std::cout#include <thread> // std::thread, std::this_thread::yield#include <mutex> // std::mutex, std::unique_lock#include <condition_variable> // std::condition_variablestd::mutex mtx;std::condition_variable cv;int cargo = 0;bool shipment_available(){ return cargo != 0;}// 消費(fèi)者線程.void consume(int n){ for (int i = 0; i < n; ++i) { std::unique_lock <std::mutex> lck(mtx); cv.wait(lck, shipment_available); std::cout << cargo << '\n'; cargo = 0; }}int main(){ std::thread consumer_thread(consume, 10); // 消費(fèi)者線程. // 主線程為生產(chǎn)者線程, 生產(chǎn) 10 個(gè)物品. for (int i = 0; i < 10; ++i) { while (shipment_available()) std::this_thread::yield(); std::unique_lock <std::mutex> lck(mtx); cargo = i + 1; cv.notify_one(); } consumer_thread.join(); return 0;}


程序執(zhí)行結(jié)果如下:


concurrency ) ./ConditionVariable-wait 12345678910


std::condition_variable::wait_for() 介紹
unconditional (1)
template <class Rep, class Period> cv_status wait_for (unique_lock<mutex>& lck, const chrono::duration<Rep,Period>& rel_time);

predicate (2)
template <class Rep, class Period, class Predicate> bool wait_for (unique_lock<mutex>& lck, const chrono::duration<Rep,Period>& rel_time, Predicate pred);

std::condition_variable::wait() 類(lèi)似,不過(guò) wait_for 可以指定一個(gè)時(shí)間段,在當(dāng)前線程收到通知或者指定的時(shí)間 rel_time 超時(shí)之前,該線程都會(huì)處于阻塞狀態(tài)。而一旦超時(shí)或者收到了其他線程的通知,wait_for 返回,剩下的處理步驟和 wait() 類(lèi)似。
另外,wait_for 的重載版本(predicte(2))的最后一個(gè)參數(shù) pred 表示 wait_for 的預(yù)測(cè)條件,只有當(dāng) pred 條件為 false 時(shí)調(diào)用 wait() 才會(huì)阻塞當(dāng)前線程,并且在收到其他線程的通知后只有當(dāng) pred 為 true 時(shí)才會(huì)被解除阻塞,因此相當(dāng)于如下代碼:
return wait_until (lck, chrono::steady_clock::now() + rel_time, std::move(pred));

請(qǐng)看下面的例子(參考),下面的例子中,主線程等待 th 線程輸入一個(gè)值,然后將 th 線程從終端接收的值打印出來(lái),在 th 線程接受到值之前,主線程一直等待,每個(gè)一秒超時(shí)一次,并打印一個(gè) ".":

復(fù)制代碼

include <iostream> // std::cout#include <thread> // std::thread#include <chrono> // std::chrono::seconds#include <mutex> // std::mutex, std::unique_lock#include <condition_variable> // std::condition_variable, std::cv_statusstd::condition_variable cv;int value;void do_read_value(){ std::cin >> value; cv.notify_one();}int main (){ std::cout << "Please, enter an integer (I'll be printing dots): \n"; std::thread th(do_read_value); std::mutex mtx; std::unique_lock<std::mutex> lck(mtx); while (cv.wait_for(lck,std::chrono::seconds(1)) == std::cv_status::timeout) { std::cout << '.'; std::cout.flush(); } std::cout << "You entered: " << value << '\n'; th.join(); return 0;}

復(fù)制代碼

std::condition_variable::wait_until 介紹
unconditional (1)
template <class Clock, class Duration> cv_status wait_until (unique_lock<mutex>& lck, const chrono::time_point<Clock,Duration>& abs_time);

predicate (2)
template <class Clock, class Duration, class Predicate> bool wait_until (unique_lock<mutex>& lck, const chrono::time_point<Clock,Duration>& abs_time, Predicate pred);

std::condition_variable::wait_for 類(lèi)似,但是 wait_until 可以指定一個(gè)時(shí)間點(diǎn),在當(dāng)前線程收到通知或者指定的時(shí)間點(diǎn) abs_time 超時(shí)之前,該線程都會(huì)處于阻塞狀態(tài)。而一旦超時(shí)或者收到了其他線程的通知,wait_until 返回,剩下的處理步驟和 wait_until() 類(lèi)似。
另外,wait_until 的重載版本(
predicte(2)
)的最后一個(gè)參數(shù) pred 表示 wait_until 的預(yù)測(cè)條件,只有當(dāng) pred 條件為 false 時(shí)調(diào)用 wait() 才會(huì)阻塞當(dāng)前線程,并且在收到其他線程的通知后只有當(dāng) pred 為 true 時(shí)才會(huì)被解除阻塞,因此相當(dāng)于如下代碼:
while (!pred()) if ( wait_until(lck,abs_time) == cv_status::timeout) return pred();return true;

std::condition_variable::notify_one() 介紹
喚醒某個(gè)等待(wait)線程。如果當(dāng)前沒(méi)有等待線程,則該函數(shù)什么也不做,如果同時(shí)存在多個(gè)等待線程,則喚醒某個(gè)線程是不確定的(unspecified)。
請(qǐng)看下例(參考):

復(fù)制代碼

include <iostream> // std::cout#include <thread> // std::thread#include <mutex> // std::mutex, std::unique_lock#include <condition_variable> // std::condition_variablestd::mutex mtx;std::condition_variable cv;int cargo = 0; // shared value by producers and consumersvoid consumer(){ std::unique_lock < std::mutex > lck(mtx); while (cargo == 0) cv.wait(lck); std::cout << cargo << '\n'; cargo = 0;}void producer(int id){ std::unique_lock < std::mutex > lck(mtx); cargo = id; cv.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;}

復(fù)制代碼

std::condition_variable::notify_all() 介紹
喚醒所有的等待(wait)線程。如果當(dāng)前沒(méi)有等待線程,則該函數(shù)什么也不做。請(qǐng)看下面的例子:

復(fù)制代碼

include <iostream> // std::cout#include <thread> // std::thread#include <mutex> // std::mutex, std::unique_lock#include <condition_variable> // std::condition_variablestd::mutex mtx; // 全局互斥鎖.std::condition_variable cv; // 全局條件變量.bool ready = false; // 全局標(biāo)志位.void do_print_id(int id){ std::unique_lock <std::mutex> lck(mtx); while (!ready) // 如果標(biāo)志位不為 true, 則等待... cv.wait(lck); // 當(dāng)前線程被阻塞, 當(dāng)全局標(biāo)志位變?yōu)?true 之后, // 線程被喚醒, 繼續(xù)往下執(zhí)行打印線程編號(hào)id. std::cout << "thread " << id << '\n';}void go(){ std::unique_lock <std::mutex> lck(mtx); ready = true; // 設(shè)置全局標(biāo)志位為 true. cv.notify_all(); // 喚醒所有線程.}int main(){ std::thread threads[10]; // spawn 10 threads: for (int i = 0; i < 10; ++i) threads[i] = std::thread(do_print_id, i); std::cout << "10 threads ready to race...\n"; go(); // go! for (auto & th:threads) th.join(); return 0;}

復(fù)制代碼

std::condition_variable_any 介紹
與 std::condition_variable 類(lèi)似,只不過(guò) std::condition_variable_any 的 wait 函數(shù)可以接受任何 lockable 參數(shù),而 std::condition_variable 只能接受 std::unique_lock<std::mutex> 類(lèi)型的參數(shù),除此以外,和 std::condition_variable 幾乎完全一樣。

std::cv_status 枚舉類(lèi)型介紹
cv_status::no_timeout

wait_for 或者 wait_until 沒(méi)有超時(shí),即在規(guī)定的時(shí)間段內(nèi)線程收到了通知。

cv_status::timeout

wait_for 或者 wait_until 超時(shí)。

std::notify_all_at_thread_exit
函數(shù)原型為:
void notify_all_at_thread_exit (condition_variable& cond, unique_lock<mutex> lck);

當(dāng)調(diào)用該函數(shù)的線程退出時(shí),所有在 cond
條件變量上等待的線程都會(huì)收到通知。請(qǐng)看下例(參考):

復(fù)制代碼

include <iostream> // std::cout#include <thread> // std::thread#include <mutex> // std::mutex, std::unique_lock#include <condition_variable> // std::condition_variablestd::mutex mtx;std::condition_variable cv;bool ready = false;void print_id (int id) { std::unique_lock<std::mutex> lck(mtx); while (!ready) cv.wait(lck); // ... std::cout << "thread " << id << '\n';}void go() { std::unique_lock<std::mutex> lck(mtx); std::notify_all_at_thread_exit(cv,std::move(lck)); ready = true;}int main (){ std::thread threads[10]; // spawn 10 threads: for (int i=0; i<10; ++i) threads[i] = std::thread(print_id,i); std::cout << "10 threads ready to race...\n"; std::thread(go).detach(); // go! for (auto& th : threads) th.join(); return 0;}

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書(shū)系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

  • 接著上節(jié) atomic,本節(jié)主要介紹condition_varible的內(nèi)容,練習(xí)代碼地址。本文參考http://...
    jorion閱讀 8,645評(píng)論 0 7
  • 本文根據(jù)眾多互聯(lián)網(wǎng)博客內(nèi)容整理后形成,引用內(nèi)容的版權(quán)歸原始作者所有,僅限于學(xué)習(xí)研究使用,不得用于任何商業(yè)用途。 互...
    深紅的眼眸閱讀 1,234評(píng)論 0 0
  • 接著上上節(jié) thread ,本節(jié)主要介紹mutex的內(nèi)容,練習(xí)代碼地址。<mutex>:該頭文件主要聲明了與互斥量...
    jorion閱讀 12,696評(píng)論 2 4
  • 以下是我最近幾個(gè)星期學(xué)習(xí)c++11做的一些記錄,包括收集的一些信息,整理的相關(guān)概念和寫(xiě)的一些測(cè)試代碼。具體相關(guān)代碼...
    在河之簡(jiǎn)閱讀 2,963評(píng)論 0 6
  • 線程的產(chǎn)生 多線程并發(fā)高級(jí)接口std::async()和類(lèi)std::future<> 1,async()使得可調(diào)用...
    龍遁流閱讀 1,062評(píng)論 0 1

友情鏈接更多精彩內(nèi)容