UNIX(多線程):16---條件變量

2021-03-02 遊戲開發司機
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_variable
std::mutex mtx; std::condition_variable cv; bool ready = false;
void do_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); ready = true; cv.notify_all(); }
int main(){ std::thread threads[10]; for (int i = 0; i < 10; ++i) threads[i] = std::thread(do_print_id, i);
std::cout << "10 threads ready to race...\n"; 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_variable
std::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);
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_status
std::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_variable
std::mutex mtx;std::condition_variable cv;
int cargo = 0;
void 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];
for (int i = 0; i < 10; ++i) { consumers[i] = std::thread(consumer); producers[i] = std::thread(producer, i + 1); }
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_variable
std::mutex mtx; std::condition_variable cv; bool ready = false;
void do_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); ready = true; cv.notify_all(); }
int main(){ std::thread threads[10]; for (int i = 0; i < 10; ++i) threads[i] = std::thread(do_print_id, i);
std::cout << "10 threads ready to race...\n"; 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_timeoutwait_for 或者 wait_until 沒有超時,即在規定的時間段內線程收到了通知。cv_status::timeoutwait_for 或者 wait_until 超時。

如果你喜歡這篇文章或公眾號,請記得分享給更多的人,點擊「分享」

相關焦點

  • Linux Qt使用POSIX多線程條件變量、互斥鎖(量)
    嘎嘎之前一直在看POSIX的多線程編程,上個周末結合自己的理解,寫了一個基於Qt的用條件變量同步線程的例子。故此來和大家一起分享,希望和大家一起交流。提到線程,如果在UI編程中,總會和一些耗時操作聯繫在一起。
  • Python中的Condition條件變量 - Python每日3題(多線程專題)
    both threadsq = Queue()t1 = Thread(target=consumer, args=(q,))t2 = Thread(target=producer, args=(q,))t1.start()t2.start()t1.join()t2.join()[Hard] 說說Condition條件變量
  • C++11線程、鎖和條件變量
    與lock_quard不同,它還支持延遲加鎖、時間鎖、遞歸鎖、鎖所有權的轉移並且還支持使用條件變量。這也是一個不可複製的類,但它是可以移動的類。_lock.unlock();  } 條件變量 C++11還提供了對另外一個同步原語的支持,這個原語就是條件變量。使用條件變量可以將一個或多個線程進入阻塞狀態,直到收到另外一個線程的通知,或者超時或者發生了虛假喚醒,才能退出阻塞狀態。
  • UNIX(多線程):14---理解線程構造函數
    ,在線程內部修改該變量,主線程的變量會改變嗎?std::thread t1(f, m); t1.join(); std::cout << m << std::endl; return 0;}// vs下:最終是:"hello"// g++編譯器:編譯報錯事實上,該代碼使用g++編譯會報錯,而使用vs2015並不會報錯,但是子線程並沒有成功改變外面的變量
  • fork() 函數與 Linux 中的多線程編程
    因為長期以來程序都是單線程的,fork()運轉正常。當20世紀90年代初期引入線程之後,fork()的適用範圍就大為縮小了。在多線程執行的情況下調用fork()函數,僅會將發起調用的線程複製到子進程中。(子進程中該線程的ID與父進程中發起fork()調用的線程ID是一樣的,因此,線程ID相同的情況有時我們需要做特殊的處理。)也就是說不能同時創建出於父進程一樣多線程的子進程。
  • C++ 條件變量使用詳解
    【導讀】:本文主要講解條件變量的詳細使用方法。condition_variable介紹在C++11中,我們可以使用條件變量(condition_variable)實現多個線程間的同步操作;當條件不滿足時,相關線程被一直阻塞,直到某種條件出現,這些線程才會被喚醒。
  • UNIX(多線程):08---線程傳參詳解,detach()陷阱,成員函數做線程函數
    線程傳參詳解,detach()陷阱,成員函數做線程函數傳遞臨時對象作為線程參數【引例】
  • Linux 多線程詳解 —— 線程安全
    線程安全中涉及到的概念:臨界資源:多線程中都能訪問到的資源臨界區:每個線程內部,訪問臨界資源的代碼,就叫臨界區
  • UNIX(多線程):07---線程啟動、結束,創建線程多法、join,detach
    線程啟動、結束,創建線程多法、join,detach範例演示線程運行的開始和結束#include
  • 探索 Android 多線程優化方法 | 開發者說·DTalk
    而且 Android 強制要求開發者在發起網絡請求時,必須在工作線程,不能在主線程,也就是開發 Android 應用必須使用多線程。既然上面說到了使用多線程是不可避免的,那使用多線程又會遇到哪些問題呢?做多線程優化是為了解決多線程的安全性和活躍性問題。
  • 走進C++11(三十)標準化條件變量 -- condition_variable
    std::condition_variable是條件變。Linux下使用 Pthread庫中的 pthread_cond_*() 函數提供了與條件變量相關的功能。和pthread_cond_*()一樣,我們可以使用條件變量(condition_variable)實現多個線程間的同步操作;當條件不滿足時,相關線程被一直阻塞,直到某種條件出現,這些線程才會被喚醒。
  • 多線程中的信號量(Semaphore)Python每日3題(多線程專題)
    這裡是Python7編程挑戰-多線程專題
  • 如何理解互斥鎖、條件變量、讀寫鎖以及自旋鎖?
    mutex無疑是最常見的多線程同步方式。其思想簡單粗暴,多線程共享一個互斥量,然後線程之間去競爭。得到鎖的線程可以進入臨界區執行代碼。當線程嘗試加鎖時,如果鎖已經被其他線程鎖定,該線程就會阻塞住,直到能成功acquire。但有時候我們不希望這樣。pthread_mutex_trylock在被其他線程鎖定時,會返回特殊錯誤碼。加鎖成返回0,僅當成功但時候,我們才能解鎖在後面進行解鎖操作!C++11開始引入了多線程庫<thread>,其中也包含了互斥鎖的API:std::muxtex 。
  • 什麼是php多線程?
    今天將要分享的是線程知識是有關於PHP的,具有一定的參考價值,希望對大家有所幫助多線程的理解所謂多線程就是在在一個進程中可以並發多個線程,每條線程並行執行不同的任務。多線程大大提高了程序的執行效率,一個多線程比單線程被作業系統調度的概率更大。而且更高效。
  • Qt多線程分享——線程局部存儲
    在多線程術語中,經常聽到一個詞就是「線程局部存儲」,英文Thread-local storage,簡稱TLS。我們今天就來看看這到底是一個什麼樣的神秘東西。1 在講解之前,我們先來看一個例子。3 再次修改現在我們想分別跟蹤統計MyThread1和MyThread2調用common_function的次數,顯然全局變量、局部靜態變量、局部變量已經無法滿足要求,那要如何做到呢。這時就用到了線程局部存儲,在Qt中對應為QThreadStorage。你可以直接運行下面的示例,看一下輸出效果。
  • UNIX(多線程):03--- 認識std::thread
    檢查當前的線程對象是否表示了一個活動的執行線程,由默認構造函數創建的線程是不能被 join 的。另外,如果某個線程 已經執行完任務,但是沒有被 join 的話,該線程依然會被認為是一個活動的執行線程,因此也是可以被 join 的。
  • 詳解 C++ 多線程的condition_variable
    一、condition_variable條件變量的介紹std::condition_variable 是條件變量,更多有關條件變量的定義參考維基百科。Linux 下使用 Pthread 庫中的 pthread_cond_*() 函數提供了與條件變量相關的功能, Windows 則參考 MSDN  。當 std::condition_variable 對象的某個 wait 函數被調用的時候,它使用 std::unique_lock(通過 std::mutex) 來鎖住當前線程。
  • Web爬蟲:多線程、異步與動態代理初步
    進入正題,使用Python3簡單實現一個單機版多線程/異步+多代理的爬蟲,沒有分布式、不談高效率,先跑起來再說,腦補開始。。。1.4 多線程多線程是實現任務並發的方式之一。在Python中實現多線程的方案比較多,最常見的是隊列和線程類que = queue.Queue()def worker():    while not que.empty():        do(que.get())threads = []nloops = 256for i in range(nloops):    t = threading.Thread(target=worker
  • Java開發多線程是如何解決安全問題的?
    Java序言:提到線程安全,可能大家首先想到的是確保接口對共享變量的操作要具備 原子性。實際上,在多線程編程中我們需要同時關注可見性,順序性和原子性。每個線程讀取共享變量時,都會將該變量加載進其對應CPU的高速緩存裡,修改該變量後,CPU會立即更新該緩存,但並不一定會立即將其寫回主內存(實際上寫回主內存的時間不可預期)。此時其它線程(尤其是不在同一個CPU上執行的線程)訪問該變量時,從主內存中讀到的就是舊的數據,而非第一個線程更新後的數據。
  • UNIX(多線程):10---線程unique_lock(下)
    obja;std::thread outMsgThread(&A::outMsgRecvQueue, &obja); std::thread inMsgThread(&A::inMsgRecvQueue, &obja);inMsgThread.join();outMsgThread.join();std::cout << "主線程結束