C++ std::async的使用總結(jié)
C++98 標(biāo)準(zhǔn)中并沒有線程庫的存在,直到 C++11 中才終于提供了多線程的標(biāo)準(zhǔn)庫,提供了管理線程、保護(hù)共享數(shù)據(jù)、線程間同步操作、原子操作等類。多線程庫對(duì)應(yīng)的頭文件是 #include <thread> ,類名為 std::thread 。
然而線程畢竟是比較貼近系統(tǒng)的東西,使用起來仍然不是很方便,特別是線程同步及獲取線程運(yùn)行結(jié)果上就更加麻煩。我們不能簡單的通過 thread.join() 得到結(jié)果,必須定義一個(gè)線程共享的變量來傳遞結(jié)果,同時(shí)還要考慮線程間的互斥問題。好在 C++11 中提供了一個(gè)相對(duì)簡單的異步接口 std::async ,通過這個(gè)接口可以簡單的創(chuàng)建線程并通過 std::future 中獲取結(jié)果。以往都是自己去封裝線程實(shí)現(xiàn)自己的async,現(xiàn)在有線程的跨平臺(tái)接口可以使用就極大的方便了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 );
第一個(gè)參數(shù)是線程的創(chuàng)建策略,有兩種策略可供選擇:
- std::launch::async:在調(diào)用async就開始創(chuàng)建線程。
- std::launch::deferred:延遲加載方式創(chuàng)建線程。調(diào)用async時(shí)不創(chuàng)建線程,直到調(diào)用了future的get或者wait時(shí)才創(chuàng)建線程。
默認(rèn)策略是: std::launch::async | std::launch::deferred 也就是兩種策略的合集,具體什么意思后面詳細(xì)再說
第二個(gè)參數(shù)是線程函數(shù)
線程函數(shù)可接受 function, lambda expression, bind expression, or another function object
第三個(gè)參數(shù)是線程函數(shù)的參數(shù)
不再說明
返回值std::future
std::future 是一個(gè)模板類,它提供了一種訪問異步操作結(jié)果的機(jī)制。從字面意思上看它表示未來,這個(gè)意思就非常貼切,因?yàn)樗皇橇⒓传@取結(jié)果但是可以在某個(gè)時(shí)候以同步的方式來獲取結(jié)果。我們可以通過查詢future的狀態(tài)來獲取異步操作的結(jié)構(gòu)。future_status有三種狀態(tài):
- deferred:異步操作還未開始
- ready:異步操作已經(jīng)完成
- timeout:異步操作超時(shí),主要用于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:超時(shí)等待返回結(jié)果,上面示例中就是對(duì)超時(shí)等待的使用展示
介紹完了 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") :
// 可能同時(shí)打印 "Hello 42" 或延遲執(zhí)行
auto a1 = std::async(&X::foo, &x, 42, "Hello");
// 以 deferred 策略調(diào)用 x.bar("world!")
// 調(diào)用 a2.get() 或 a2.wait() 時(shí)打印 "world!"
auto a2 = std::async(std::launch::deferred, &X::bar, x, "world!");
// 以 async 策略調(diào)用 X()(43) :
// 同時(shí)打印 "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 是異步操作做了一個(gè)很好的封裝,使我們不用關(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ù)可能只會(huì)在std::async返回的future對(duì)象調(diào)用get或wait時(shí)執(zhí)行。那就是,執(zhí)行會(huì)推遲到其中一個(gè)調(diào)用發(fā)生。當(dāng)調(diào)用get或wait時(shí),函數(shù)會(huì)同步執(zhí)行,即調(diào)用者會(huì)阻塞直到函數(shù)運(yùn)行結(jié)束。如果get或wait沒有被調(diào)用,函數(shù)就絕對(duì)不會(huì)執(zhí)行。
兩者策略都很明確,然而該函數(shù)的默認(rèn)策略卻很有趣,它不是你顯示指定的,也就是第一個(gè)函數(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是否會(huì)在哪個(gè)線程執(zhí)行,甚至無法預(yù)知會(huì)不會(huì)被執(zhí)行,因?yàn)閒unc可能會(huì)被調(diào)度為推遲執(zhí)行,即調(diào)用get或wait的時(shí)候執(zhí)行,而get或wait是否會(huì)被執(zhí)行或者在哪個(gè)線程執(zhí)行都無法預(yù)知。
同時(shí)這種調(diào)度策略的靈活性還會(huì)混淆使用thread_local變量,這意味著如果func寫或讀這種線程本地存儲(chǔ)(Thread Local Storage,TLS),預(yù)知取到哪個(gè)線程的本地變量是不可能的。
它也影響了基于wait循環(huán)中的超時(shí)情況,因?yàn)檎{(diào)度策略可能為 deferred 的,調(diào)用wait_for或者wait_until會(huì)返回值std::launch::deferred。這意味著下面的循環(huán),看起來最終會(huì)停止,但是,實(shí)際上可能會(huì)一直運(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)不會(huì)發(fā)生
{
...
}
為避免陷入死循環(huán),我們必須檢查future是否把任務(wù)推遲,然而future無法獲知任務(wù)是否被推遲,一個(gè)好的技巧就是通過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就緒
}
有人可能會(huì)說既然有這么多缺點(diǎn)為啥還要用它,因?yàn)楫吘刮覀兛紤]的極限情況下的可能,有時(shí)候我不要求它是并發(fā)還是同步執(zhí)行,也不需要考慮修改那個(gè)線程thread_local變量,同時(shí)也能接受可能任務(wù)永遠(yuǎn)不會(huì)執(zhí)行,那么這種方式就是一種方便且高效的調(diào)度策略。
綜上所述,我們總結(jié)出以下幾點(diǎn):
- std::async的默認(rèn)調(diào)度策略既允許任務(wù)異步執(zhí)行,又允許任務(wù)同步執(zhí)行。
- 默認(rèn)策略靈活性導(dǎo)致了使用thread_local變量時(shí)的不確定性,它隱含著任務(wù)可能不會(huì)執(zhí)行,它還影響了基于超時(shí)的wait調(diào)用的程序邏輯。
- 如果異步執(zhí)行是必需的,指定std::launch::async發(fā)射策略。
到此這篇關(guān)于C++ std::async的使用總結(jié)的文章就介紹到這了,更多相關(guān)C++ std::async內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
詳解C語言中的ttyname()函數(shù)和isatty()函數(shù)的用法
這篇文章主要介紹了C語言中的ttyname()函數(shù)和isatty()函數(shù)的用法,是C語言入門學(xué)習(xí)中的基礎(chǔ)知識(shí),需要的朋友可以參考下2015-09-09
c語言實(shí)現(xiàn)二叉查找樹實(shí)例方法
這篇文章主要介紹了一個(gè)c語言版的二叉查找樹實(shí)現(xiàn),二叉查找樹,支持的操作包括:SERACH、MINIMUM、MAXIMUM、PREDECESSOR、SUCCESSOR、INSERT、DELETE,大家參考使用吧2013-11-11
C語言實(shí)現(xiàn)掃雷游戲詳細(xì)代碼實(shí)例
這篇文章主要介紹了C語言實(shí)現(xiàn)掃雷游戲詳細(xì)代碼實(shí)例,有感興趣的同學(xué)可以借鑒參考下2021-02-02
C語言如何利用ASCII碼表統(tǒng)計(jì)字符串每個(gè)字符出現(xiàn)的次數(shù)
這篇文章主要介紹了C語言如何利用ASCII碼表統(tǒng)計(jì)字符串每個(gè)字符出現(xiàn)的次數(shù),具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2023-01-01

