std::condition_variable 詳解

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

首先我們來看一個簡單的[例子]



#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 do_print_id(int id){ std::unique_lock <std::mutex> lck(mtx); while (!ready) // 如果標志位不為 true, 則等待... cv.wait(lck); // 當前線程被阻塞, 當全局標志位變為 true 之后, // 線程被喚醒, 繼續往下執行打印線程編號id. std::cout << "thread " << id << '\n';}void go(){ std::unique_lock <std::mutex> lck(mtx); ready = true; // 設置全局標志位為 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;}

執行結果如下:


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

好了,對條件變量有了一個基本的了解之后,我們來看看 std::condition_variable 的各個成員函數。
std::condition_variable 構造函數
default (1)
condition_variable();

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

std::condition_variable 的拷貝構造函數被禁用,只提供了默認構造函數。
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() 函數。當前線程調用 wait() 后將被阻塞(此時當前線程應該獲得了鎖(mutex),不妨設獲得鎖 lck),直到另外某個線程調用 notify_* 喚醒了當前線程。
在線程被阻塞時,該函數會自動調用 lck.unlock() 釋放鎖,使得其他被阻塞在鎖競爭上的線程得以繼續執行。
另外,一旦當前線程獲得通知(notified,通常是另外某個線程調用 notify_* 喚醒了當前線程),wait() 函數也是自動調用 lck.lock(),使得 lck 的狀態和 wait 函數被調用時相同。在第二種情況下(即設置了 Predicate),只有當 pred 條件為 false 時調用 wait() 才會阻塞當前線程,并且在收到其他線程的通知后只有當 pred 為 true 時才會被解除阻塞。因此第二種情況類似以下代碼:
while (!pred()) wait(lck);

請看下面例子(參考):


#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;}// 消費者線程.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); // 消費者線程. // 主線程為生產者線程, 生產 10 個物品. 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;}


程序執行結果如下:


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() 類似,不過 wait_for 可以指定一個時間段,在當前線程收到通知或者指定的時間 rel_time 超時之前,該線程都會處于阻塞狀態。而一旦超時或者收到了其他線程的通知,wait_for 返回,剩下的處理步驟和 wait() 類似。
另外,wait_for 的重載版本(predicte(2))的最后一個參數 pred 表示 wait_for 的預測條件,只有當 pred 條件為 false 時調用 wait() 才會阻塞當前線程,并且在收到其他線程的通知后只有當 pred 為 true 時才會被解除阻塞,因此相當于如下代碼:
return wait_until (lck, chrono::steady_clock::now() + rel_time, std::move(pred));

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

復制代碼

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;}

復制代碼

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 類似,但是 wait_until 可以指定一個時間點,在當前線程收到通知或者指定的時間點 abs_time 超時之前,該線程都會處于阻塞狀態。而一旦超時或者收到了其他線程的通知,wait_until 返回,剩下的處理步驟和 wait_until() 類似。
另外,wait_until 的重載版本(
predicte(2)
)的最后一個參數 pred 表示 wait_until 的預測條件,只有當 pred 條件為 false 時調用 wait() 才會阻塞當前線程,并且在收到其他線程的通知后只有當 pred 為 true 時才會被解除阻塞,因此相當于如下代碼:
while (!pred()) if ( wait_until(lck,abs_time) == cv_status::timeout) return pred();return true;

std::condition_variable::notify_one() 介紹
喚醒某個等待(wait)線程。如果當前沒有等待線程,則該函數什么也不做,如果同時存在多個等待線程,則喚醒某個線程是不確定的(unspecified)。
請看下例(參考):

復制代碼

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;}

復制代碼

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

復制代碼

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 do_print_id(int id){ std::unique_lock <std::mutex> lck(mtx); while (!ready) // 如果標志位不為 true, 則等待... cv.wait(lck); // 當前線程被阻塞, 當全局標志位變為 true 之后, // 線程被喚醒, 繼續往下執行打印線程編號id. std::cout << "thread " << id << '\n';}void go(){ std::unique_lock <std::mutex> lck(mtx); ready = true; // 設置全局標志位為 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;}

復制代碼

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

std::cv_status 枚舉類型介紹
cv_status::no_timeout

wait_for 或者 wait_until 沒有超時,即在規定的時間段內線程收到了通知。

cv_status::timeout

wait_for 或者 wait_until 超時。

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

當調用該函數的線程退出時,所有在 cond
條件變量上等待的線程都會收到通知。請看下例(參考):

復制代碼

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;}

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。

推薦閱讀更多精彩內容

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