C++ std::condition_variable 條件變量用法解析
1.簡(jiǎn)介
condition_variable(條件變量)是 C++11 中提供的一種多線程同步機(jī)制,它允許一個(gè)或多個(gè)線程等待另一個(gè)線程發(fā)出通知,以便能夠有效地進(jìn)行線程同步。
condition_variable 需要與 mutex(互斥鎖)一起使用。當(dāng)線程需要等待某個(gè)條件變成真時(shí),它會(huì)獲取一個(gè)互斥鎖,然后在條件變量上等待,等待期間會(huì)自動(dòng)釋放互斥鎖。另一個(gè)線程在滿足條件后會(huì)獲取相同的互斥鎖,并調(diào)用條件變量的 notify_one() 或 notify_all() 函數(shù)來喚醒等待的線程。
條件變量是實(shí)現(xiàn)復(fù)雜線程同步和通信的重要工具,用于避免線程的忙等待和提高性能。
2.等待函數(shù)
condition_variable 有三個(gè)等待函數(shù):wait()、wait_for() 和 wait_util()。
這三個(gè)函數(shù)需要與互斥鎖一起使用,以互斥的方式訪問共享資源,并阻塞線程,等待通知。
wait()
void wait(std::unique_lock<std::mutex>& lock); template <class Predicate> void wait (unique_lock<mutex>& lck, Predicate pred);
wait() 函數(shù)用于阻塞線程并等待喚醒。
在調(diào)用 wait() 之前,必須獲取一個(gè)獨(dú)占鎖(std::unique_lock)并將它傳遞給 wait() 函數(shù)。
如果條件變量當(dāng)前不滿足,線程將被阻塞,同時(shí)釋放鎖,使得其他線程可以繼續(xù)執(zhí)行。
當(dāng)另一個(gè)線程調(diào)用 notify_one() 或 notify_all() 來通知條件變量時(shí),被阻塞的線程將被喚醒,并再次嘗試獲取鎖。
wait() 函數(shù)返回時(shí),鎖會(huì)再次被持有。
wait() 函數(shù)有一個(gè)帶謂詞的版本,可以簡(jiǎn)化對(duì)條件的判斷。僅僅有當(dāng) pred 條件為 false 時(shí)調(diào)用 wait() 才會(huì)阻塞當(dāng)前線程,解決了的喚醒丟失問題。而且在收到其它線程的通知后僅僅有當(dāng) pred 為 true 時(shí)才會(huì)被解除堵塞,解決了虛假喚醒的問題。
用法如下:
std::condition_variable cv; std::mutex mtx; bool condition = false; void worker() { std::unique_lock<std::mutex> lock(mtx); cv.wait(lock, []{ return condition; }); // 等待條件滿足 // ... }
wait_for()
template <class Rep, class Period> cv_status wait_for(std::unique_lock<std::mutex>& lock, const std::chrono::duration<Rep, Period>& rel_time); template <class Rep, class Period, class Predicate> bool wait_for (unique_lock<mutex>& lck, const chrono::duration<Rep,Period>& rel_time, Predicate pred);
wait_for() 函數(shù)用于阻塞線程并等待喚醒,但與 wait() 不同,它可以設(shè)置一個(gè)超時(shí)時(shí)間。
在調(diào)用 wait_for() 之前,必須獲取一個(gè)獨(dú)占鎖(std::unique_lock)并將它傳遞給 wait_for() 函數(shù)。
如果條件變量在指定的超時(shí)時(shí)間內(nèi)變?yōu)闈M足,線程將被喚醒,并且 wait_for() 返回 cv_status::no_timeout。
如果超時(shí)時(shí)間到期且仍未收到喚醒通知,wait_for() 返回 cv_status::timeout,線程繼續(xù)執(zhí)行。
wait_for() 函數(shù)同樣有一個(gè)謂詞版本,用法同 wait() 函數(shù)。
wait_until()
template <class Clock, class Duration> cv_status wait_until(std::unique_lock<std::mutex>& lock, const std::chrono::time_point<Clock, Duration>& abs_time); template <class Clock, class Duration, class Predicate> bool wait_until(unique_lock<mutex>& lck, const chrono::time_point<Clock,Duration>& abs_time, Predicate pred);
wait_until 接受一個(gè)絕對(duì)時(shí)間點(diǎn)作為參數(shù)。
線程將等待直到指定的絕對(duì)時(shí)間點(diǎn),如果在該時(shí)間點(diǎn)之前條件變量滿足,它將返回并繼續(xù)執(zhí)行。
如果到達(dá)指定時(shí)間點(diǎn)仍未收到喚醒通知,wait_until 返回 cv_status::timeout,線程繼續(xù)執(zhí)行。
wait_until() 函數(shù)同樣有一個(gè)謂詞版本,用法同 wait() 函數(shù)。
3.通知函數(shù)
通知函數(shù)有 notify_one() 和 notify_all()。
這兩個(gè)函數(shù)都用于喚醒等待線程,以便它們可以繼續(xù)執(zhí)行。notify_all() 用于廣播通知,以確保所有等待線程都有機(jī)會(huì)檢查條件是否滿足,而 notify_one() 用于選擇性通知一個(gè)等待線程。
notify_one()
void notify_one() noexcept;
notify_one() 用于喚醒等待在條件變量上的單個(gè)線程。
如果有多個(gè)線程在條件變量上等待,只有其中一個(gè)線程會(huì)被喚醒,具體是哪個(gè)線程 C++ 標(biāo)準(zhǔn)并未明確,所以是不確定的。
被喚醒的線程將嘗試獲取與條件變量關(guān)聯(lián)的互斥鎖,一旦成功獲取鎖,它可以繼續(xù)執(zhí)行。
notify_all()
void notify_all() noexcept;
notify_all() 用于喚醒等待在條件變量上的所有線程。
如果有多個(gè)線程在條件變量上等待,所有這些線程都會(huì)被喚醒。
喚醒的線程將競(jìng)爭(zhēng)獲取與條件變量關(guān)聯(lián)的互斥鎖,然后可以繼續(xù)執(zhí)行。
4.注意事項(xiàng)
在使用 condition_variable 時(shí)需要注意以下幾點(diǎn):
1.需要與互斥量一起使用,等待前要鎖定互斥量
std::condition_variable 必須與 std::unique_lock 一起使用,需要在持有 mutex 的情況下調(diào)用 wait() 函數(shù),以確保在線程等待條件時(shí)互斥訪問共享資源,從而避免競(jìng)態(tài)條件(Race Condition)。共享資源包括等待的條件,以及線程等待隊(duì)列。
2.注意虛假喚醒和喚醒丟失
虛假喚醒(spurious wakeup)指一個(gè)或多個(gè)線程被喚醒,但沒有實(shí)際的條件變化或通知發(fā)生。這些線程被認(rèn)為是"虛假喚醒"。
虛假喚醒通常由操作系統(tǒng)或 C++ 標(biāo)準(zhǔn)庫(kù)的實(shí)現(xiàn)引發(fā),這是多線程環(huán)境中的一種正常行為。雖然它可能看起來不合理,但是在某些情況下,它是必要的,因?yàn)椴僮飨到y(tǒng)或標(biāo)準(zhǔn)庫(kù)可能需要在內(nèi)部執(zhí)行一些資源管理或線程調(diào)度操作,這可能導(dǎo)致線程被喚醒。
喚醒丟失(wakeup loss)指發(fā)送方在接收方進(jìn)入等待狀態(tài)之前發(fā)送通知,結(jié)果就是導(dǎo)致通知消失。
為了解決虛假喚醒和喚醒丟失的問題,需要使用一個(gè)變量(通常是 bool 類型的變量)來表示等待的條件,線程在等待前和等待后檢查該條件是否滿足。
3.不要忽略 wait_for 和 wait_until 函數(shù)返回值
wait_for 和 wait_until 函數(shù)的返回值應(yīng)該被檢查,以判斷是因?yàn)槌瑫r(shí)還是因?yàn)楸煌ㄖ祷亍?/p>
4.不要在鎖內(nèi)部執(zhí)行耗時(shí)操作
盡量避免在鎖內(nèi)部執(zhí)行可能會(huì)阻塞或耗時(shí)較長(zhǎng)的操作,因?yàn)檫@會(huì)導(dǎo)致其他線程在等待條件時(shí)被阻塞。
5.避免死鎖
確保你的線程同步邏輯不會(huì)導(dǎo)致死鎖,例如,不要在持有互斥鎖的情況下調(diào)用可能再次嘗試獲取同一個(gè)鎖的函數(shù)。
6.小心使用 std::condition_variable_any
std::condition_variable_any 是通用的條件變量,可以與不同類型的互斥量一起使用。但要小心,因?yàn)樗男阅芸赡懿蝗缗c std::mutex 直接關(guān)聯(lián)的 std::condition_variable。
總之,在多線程編程中使用 std::condition_variable 時(shí),要謹(jǐn)慎考慮同步邏輯,確保線程安全性,防止死鎖,以及正確處理?xiàng)l件等待和通知。多線程編程通常很復(fù)雜,需要仔細(xì)思考和測(cè)試。
5.使用示例
#include <iostream> #include <thread> #include <mutex> #include <condition_variable> std::mutex mtx; // 全局相互排斥鎖。 std::condition_variable cv; // 全局條件變量。 bool ready = false; // 全局標(biāo)志位。 void print_id(int id) { std::unique_lock <std::mutex> lck(mtx); // 假設(shè)標(biāo)志位不為 true, 則等待... while (!ready){ // 當(dāng)前線程被堵塞,等待被喚醒。 cv.wait(lck); } // 線程被喚醒, 繼續(xù)往下運(yùn)行打印線程編號(hào)id。 std::cout << "thread " << id << std::endl; } void go() { std::unique_lock<std::mutex> lck(mtx); ready = true; // 設(shè)置全局標(biāo)志位為 true. cv.notify_all(); // 喚醒全部線程. } int main() { std::thread threads[10]; for (int i = 0; i < 10; ++i) { threads[i] = std::thread(print_id, i); } std::cout << "10 threads ready to race..." << std::endl; go(); // 等待所有線程執(zhí)行完成。 for (auto& th : threads) { th.join(); } return 0; }
編譯運(yùn)行輸出:
10 threads ready to race...
thread 0
thread 1
thread 2
thread 9
thread 4
thread 6
thread 5
thread 7
thread 8
thread 3
多次運(yùn)行結(jié)果是不定的,因?yàn)榫€程調(diào)度的順序是不確定的。
參考文獻(xiàn)
std::condition_variable - cplusplus.com
notify_one() choose which thread to unblock?
c++ - Why ‘wait with predicate’ solves the ‘lost wakeup’ - stackoverflow
到此這篇關(guān)于C++ std::condition_variable 條件變量用法的文章就介紹到這了,更多相關(guān)C++ std::condition_variable 條件變量?jī)?nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
C語(yǔ)言數(shù)據(jù)結(jié)構(gòu)之鏈隊(duì)列的基本操作
這篇文章主要為大家介紹了C語(yǔ)言之鏈隊(duì)列的基本操作,具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下,希望能夠給你帶來幫助2021-12-12C++使用郵件槽實(shí)現(xiàn)ShellCode跨進(jìn)程傳輸
在計(jì)算機(jī)安全領(lǐng)域,進(jìn)程間通信(IPC)一直是一個(gè)備受關(guān)注的話題,在本文中,我們將探討如何使用Windows郵件槽(Mailslot)實(shí)現(xiàn)ShellCode的跨進(jìn)程傳輸,需要的可以參考下2023-12-12C++ 實(shí)現(xiàn)帶監(jiān)視哨的順序查找算法
這篇文章主要介紹了C++ 實(shí)現(xiàn)帶監(jiān)視哨的順序查找算法,本文通過實(shí)例代碼給大家介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友可以參考下2020-03-03C語(yǔ)言實(shí)現(xiàn)客房管理系統(tǒng)
這篇文章主要為大家詳細(xì)介紹了C語(yǔ)言實(shí)現(xiàn)客房管理系統(tǒng),文中示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2022-08-08C++中的類型查詢之探索typeid和type_info(推薦)
C++ 是一種靜態(tài)類型語(yǔ)言,這意味著每個(gè)變量的類型在編譯時(shí)就已經(jīng)確定,在這篇技術(shù)分享中,我們將探討 C++ 中的 typeid 和 type_info,以及如何使用它們來獲取類型信息,需要的朋友可以參考下2024-05-05