C++ std::async的使用總結(jié)
C++98 標(biāo)準(zhǔn)中并沒有線程庫的存在,直到 C++11 中才終于提供了多線程的標(biāo)準(zhǔn)庫,提供了管理線程、保護(hù)共享數(shù)據(jù)、線程間同步操作、原子操作等類。多線程庫對應(yīng)的頭文件是 #include <thread> ,類名為 std::thread 。
然而線程畢竟是比較貼近系統(tǒng)的東西,使用起來仍然不是很方便,特別是線程同步及獲取線程運(yùn)行結(jié)果上就更加麻煩。我們不能簡單的通過 thread.join() 得到結(jié)果,必須定義一個線程共享的變量來傳遞結(jié)果,同時還要考慮線程間的互斥問題。好在 C++11 中提供了一個相對簡單的異步接口 std::async ,通過這個接口可以簡單的創(chuàng)建線程并通過 std::future 中獲取結(jié)果。以往都是自己去封裝線程實(shí)現(xiàn)自己的async,現(xiàn)在有線程的跨平臺接口可以使用就極大的方便了C++多線程編程。
先看一下 std::async 的函數(shù)原型
//(C++11 起) (C++17 前) template< class Function, class... Args> std::future<std::result_of_t<std::decay_t<Function>(std::decay_t<Args>...)>> async( Function&& f, Args&&... args ); //(C++11 起) (C++17 前) template< class Function, class... Args > std::future<std::result_of_t<std::decay_t<Function>(std::decay_t<Args>...)>> async( std::launch policy, Function&& f, Args&&... args );
第一個參數(shù)是線程的創(chuàng)建策略,有兩種策略可供選擇:
- std::launch::async:在調(diào)用async就開始創(chuàng)建線程。
- std::launch::deferred:延遲加載方式創(chuàng)建線程。調(diào)用async時不創(chuàng)建線程,直到調(diào)用了future的get或者wait時才創(chuàng)建線程。
默認(rèn)策略是: std::launch::async | std::launch::deferred
也就是兩種策略的合集,具體什么意思后面詳細(xì)再說
第二個參數(shù)是線程函數(shù)
線程函數(shù)可接受 function, lambda expression, bind expression, or another function object
第三個參數(shù)是線程函數(shù)的參數(shù)
不再說明
返回值std::future
std::future 是一個模板類,它提供了一種訪問異步操作結(jié)果的機(jī)制。從字面意思上看它表示未來,這個意思就非常貼切,因?yàn)樗皇橇⒓传@取結(jié)果但是可以在某個時候以同步的方式來獲取結(jié)果。我們可以通過查詢future的狀態(tài)來獲取異步操作的結(jié)構(gòu)。future_status有三種狀態(tài):
- deferred:異步操作還未開始
- ready:異步操作已經(jīng)完成
- timeout:異步操作超時,主要用于std::future .wait_for()
示例:
//查詢future的狀態(tài) std::future_status status; do { status = future.wait_for(std::chrono::seconds(1)); if (status == std::future_status::deferred) { std::cout << "deferred" << std::endl; } else if (status == std::future_status::timeout) { std::cout << "timeout" << std::endl; } else if (status == std::future_status::ready) { std::cout << "ready!" << std::endl; } } while (status != std::future_status::ready);
std::future 獲取結(jié)果的方式有三種:
- get:等待異步操作結(jié)束并返回結(jié)果
- wait:等待異步操作結(jié)束,但沒有返回值
- waite_for:超時等待返回結(jié)果,上面示例中就是對超時等待的使用展示
介紹完了 std::async 的函數(shù)原型,那么它到底該如何使用呢?
std::async 的基本用法: 示例鏈接
#include <iostream> #include <vector> #include <algorithm> #include <numeric> #include <future> #include <string> #include <mutex> std::mutex m; struct X { void foo(int i, const std::string& str) { std::lock_guard<std::mutex> lk(m); std::cout << str << ' ' << i << '\n'; } void bar(const std::string& str) { std::lock_guard<std::mutex> lk(m); std::cout << str << '\n'; } int operator()(int i) { std::lock_guard<std::mutex> lk(m); std::cout << i << '\n'; return i + 10; }}; template <typename RandomIt>int parallel_sum(RandomIt beg, RandomIt end){ auto len = end - beg; if (len < 1000) return std::accumulate(beg, end, 0); RandomIt mid = beg + len/2; auto handle = std::async(std::launch::async, parallel_sum<RandomIt>, mid, end); int sum = parallel_sum(beg, mid); return sum + handle.get(); } int main(){ std::vector<int> v(10000, 1); std::cout << "The sum is " << parallel_sum(v.begin(), v.end()) << '\n'; X x; // 以默認(rèn)策略調(diào)用 x.foo(42, "Hello") : // 可能同時打印 "Hello 42" 或延遲執(zhí)行 auto a1 = std::async(&X::foo, &x, 42, "Hello"); // 以 deferred 策略調(diào)用 x.bar("world!") // 調(diào)用 a2.get() 或 a2.wait() 時打印 "world!" auto a2 = std::async(std::launch::deferred, &X::bar, x, "world!"); // 以 async 策略調(diào)用 X()(43) : // 同時打印 "43" auto a3 = std::async(std::launch::async, X(), 43); a2.wait(); // 打印 "world!" std::cout << a3.get() << '\n'; // 打印 "53" } // 若 a1 在此點(diǎn)未完成,則 a1 的析構(gòu)函數(shù)在此打印 "Hello 42"
可能的結(jié)果
The sum is 10000
43
world!
53
Hello 42
由此可見, std::async 是異步操作做了一個很好的封裝,使我們不用關(guān)注線程創(chuàng)建內(nèi)部細(xì)節(jié),就能方便的獲取異步執(zhí)行狀態(tài)和結(jié)果,還可以指定線程創(chuàng)建策略。
深入理解線程創(chuàng)建策略
- std::launch::async調(diào)度策略意味著函數(shù)必須異步執(zhí)行,即在另一線程執(zhí)行。
- std::launch::deferred調(diào)度策略意味著函數(shù)可能只會在std::async返回的future對象調(diào)用get或wait時執(zhí)行。那就是,執(zhí)行會推遲到其中一個調(diào)用發(fā)生。當(dāng)調(diào)用get或wait時,函數(shù)會同步執(zhí)行,即調(diào)用者會阻塞直到函數(shù)運(yùn)行結(jié)束。如果get或wait沒有被調(diào)用,函數(shù)就絕對不會執(zhí)行。
兩者策略都很明確,然而該函數(shù)的默認(rèn)策略卻很有趣,它不是你顯示指定的,也就是第一個函數(shù)原型中所用的策略即 std::launch::async | std::launch::deferred
,c++標(biāo)準(zhǔn)中給出的說明是:
進(jìn)行異步執(zhí)行還是惰性求值取決于實(shí)現(xiàn)
auto future = std::async(func); // 使用默認(rèn)發(fā)射模式執(zhí)行func
這種調(diào)度策略我們沒有辦法預(yù)知函數(shù)func是否會在哪個線程執(zhí)行,甚至無法預(yù)知會不會被執(zhí)行,因?yàn)閒unc可能會被調(diào)度為推遲執(zhí)行,即調(diào)用get或wait的時候執(zhí)行,而get或wait是否會被執(zhí)行或者在哪個線程執(zhí)行都無法預(yù)知。
同時這種調(diào)度策略的靈活性還會混淆使用thread_local變量,這意味著如果func寫或讀這種線程本地存儲(Thread Local Storage,TLS),預(yù)知取到哪個線程的本地變量是不可能的。
它也影響了基于wait循環(huán)中的超時情況,因?yàn)檎{(diào)度策略可能為 deferred 的,調(diào)用wait_for或者wait_until會返回值std::launch::deferred。這意味著下面的循環(huán),看起來最終會停止,但是,實(shí)際上可能會一直運(yùn)行:
void func() // f睡眠1秒后返回 { std::this_thread::sleep_for(1); } auto future = std::async(func); // (概念上)異步執(zhí)行f while(fut.wait_for(100ms) != // 循環(huán)直到f執(zhí)行結(jié)束 std::future_status::ready) // 但這可能永遠(yuǎn)不會發(fā)生 { ... }
為避免陷入死循環(huán),我們必須檢查future是否把任務(wù)推遲,然而future無法獲知任務(wù)是否被推遲,一個好的技巧就是通過wait_for(0)來獲取future_status是否是deferred:
auto future = std::async(func); // (概念上)異步執(zhí)行f if (fut.wait_for(0) == std::future_status::deferred) // 如果任務(wù)被推遲 { ... // fut使用get或wait來同步調(diào)用f } else { // 任務(wù)沒有被推遲 while(fut.wait_for(100ms) != std::future_status::ready) { // 不可能無限循環(huán) ... // 任務(wù)沒有被推遲也沒有就緒,所以做一些并發(fā)的事情直到任務(wù)就緒 } ... // fut就緒 }
有人可能會說既然有這么多缺點(diǎn)為啥還要用它,因?yàn)楫吘刮覀兛紤]的極限情況下的可能,有時候我不要求它是并發(fā)還是同步執(zhí)行,也不需要考慮修改那個線程thread_local變量,同時也能接受可能任務(wù)永遠(yuǎn)不會執(zhí)行,那么這種方式就是一種方便且高效的調(diào)度策略。
綜上所述,我們總結(jié)出以下幾點(diǎn):
- std::async的默認(rèn)調(diào)度策略既允許任務(wù)異步執(zhí)行,又允許任務(wù)同步執(zhí)行。
- 默認(rèn)策略靈活性導(dǎo)致了使用thread_local變量時的不確定性,它隱含著任務(wù)可能不會執(zhí)行,它還影響了基于超時的wait調(diào)用的程序邏輯。
- 如果異步執(zhí)行是必需的,指定std::launch::async發(fā)射策略。
到此這篇關(guān)于C++ std::async的使用總結(jié)的文章就介紹到這了,更多相關(guān)C++ std::async內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
C++實(shí)現(xiàn)學(xué)生選課系統(tǒng)的思路與詳細(xì)過程
C語言是在國內(nèi)外廣泛使用的一種計算機(jī)語言,下面這篇文章主要給大家介紹了關(guān)于C++實(shí)現(xiàn)學(xué)生選課系統(tǒng)的思路與詳細(xì)過程,文中通過實(shí)例代碼介紹的非常詳細(xì),需要的朋友可以參考下2023-01-01

C語言深入講解動態(tài)內(nèi)存分配函數(shù)的使用

C語言FlappyBird飛揚(yáng)的小鳥實(shí)現(xiàn)開發(fā)流程

解析sizeof, strlen, 指針以及數(shù)組作為函數(shù)參數(shù)的應(yīng)用