c++98
標準中並沒有執行緒庫的存在,直到c++11
中才終於提供了多執行緒的標準庫,提供了管理執行緒、保護共享資料、執行緒間同步操作、原子操作等類。多執行緒庫對應的標頭檔案是#include
,類名為std::thread
。
然而執行緒畢竟是比較貼近系統的東西,使用起來仍然不是很方便,特別是執行緒同步及獲取執行緒執行結果上就更加麻煩。我們不能簡單的通過thread.join()
得到結果,必須定義乙個執行緒共享的變數來傳遞結果,同時還要考慮執行緒間的互斥問題。好在c++11
中提供了乙個相對簡單的非同步介面std::async
,通過這個介面可以簡單的建立執行緒並通過std::future
中獲取結果。以往都是自己去封裝執行緒實現自己的async,現在有執行緒的跨平台介面可以使用就極大的方便了c++多執行緒程式設計。
先看一下std::async
的函式原型
//(c++11 起) (c++17 前)
template< class function, class... args>
std::future(std::decay_t...)>>
async( function&& f, args&&... args );
//(c++11 起) (c++17 前)
template< class function, class... args >
std::future(std::decay_t...)>>
async( std::launch policy, function&& f, args&&... args );
第乙個引數是執行緒的建立策略,有兩種策略可供選擇:
預設策略是:std::launch::async | std::launch::deferred
也就是兩種策略的合集,具體什麼意思後面詳細再說
第二個引數是執行緒函式
執行緒函式可接受function, lambda expression, bind expression, or another function object
第三個引數是執行緒函式的引數
不再說明
返回值std::future
std::future
是乙個模板類,它提供了一種訪問非同步操作結果的機制。從字面意思上看它表示未來,這個意思就非常貼切,因為她不是立即獲取結果但是可以在某個時候以同步的方式來獲取結果。我們可以通過查詢future的狀態來獲取非同步操作的結構。future_status有三種狀態:
示例:
//查詢future的狀態
std::future_status status;
do else if (status == std::future_status::timeout) else if (status == std::future_status::ready)
} while (status != std::future_status::ready);
std::future
獲取結果的方式有三種:
介紹完了std::async
的函式原型,那麼它到底該如何使用呢?
std::async
的基本用法:
#include #include #include #include #include #include #include std::mutex m;
struct x
void bar(const std::string& str)
int operator()(int i) };
template int parallel_sum(randomit beg, randomit end)
int main() // 若 a1 在此點未完成,則 a1 的析構函式在此列印 "hello 42"
可能的結果
the sum is 10000
43world!
53hello 42
由此可見,std::async
是非同步操作做了乙個很好的封裝,使我們不用關注執行緒建立內部細節,就能方便的獲取非同步執行狀態和結果,還可以指定執行緒建立策略。
深入理解執行緒建立策略
兩者策略都很明確,然而該函式的預設策略卻很有趣,它不是你顯示指定的,也就是第乙個函式原型中所用的策略即std::launch::async | std::launch::deferred
,c++標準中給出的說明是:
進行非同步執行還是惰性求值取決於實現
auto future = std::async(func); // 使用預設發射模式執行func
這種排程策略我們沒有辦法預知函式func是否會在哪個執行緒執行,甚至無法預知會不會被執行,因為func可能會被排程為推遲執行,即呼叫get或wait的時候執行,而get或wait是否會被執行或者在哪個執行緒執行都無法預知。
同時這種排程策略的靈活性還會混淆使用thread_local變數,這意味著如果func寫或讀這種執行緒本地儲存(thread local storage,tls),預知取到哪個執行緒的本地變數是不可能的。
它也影響了基於wait迴圈中的超時情況,因為排程策略可能為deferred
的,呼叫wait_for或者wait_until會返回值std::launch::deferred。這意味著下面的迴圈,看起來最終會停止,但是,實際上可能會一直執行:
void func() // f睡眠1秒後返回
auto future = std::async(func); // (概念上)非同步執行f
while(fut.wait_for(100ms) != // 迴圈直到f執行結束
std::future_status::ready) // 但這可能永遠不會發生
為避免陷入死迴圈,我們必須檢查future是否把任務推遲,然而future無法獲知任務是否被推遲,乙個好的技巧就是通過wait_for(0)來獲取future_status是否是deferred:
auto future = std::async(func); // (概念上)非同步執行f
if (fut.wait_for(0) == std::future_status::deferred) // 如果任務被推遲
else
... // fut就緒
}
有人可能會說既然有這麼多缺點為啥還要用它,因為畢竟我們考慮的極限情況下的可能,有時候我不要求它是併發還是同步執行,也不需要考慮修改那個執行緒thread_local變數,同時也能接受可能任務永遠不會執行,那麼這種方式就是一種方便且高效的排程策略。
綜上所述,我們總結出以下幾點:
std async的使用總結
c 98標準中並沒有執行緒庫的存在,直到c 11中才終於提供了多執行緒的標準庫,提供了管理執行緒 保護共享資料 執行緒間同步操作 原子操作等類。多執行緒庫對應的標頭檔案是 include,類名為std thread。然而執行緒畢竟是比較貼近系統的東西,使用起來仍然不是很方便,特別是執行緒同步及獲取執...
乙個規模大的計算任務 std async
乙個規模大的計算任務 std async很方便,把更多的計算工作放在更多的肩上來承擔。下面例子中演示了四個非同步呼叫對標積的計算 include include include include include include using namespace std static const int ...
用C 11的std async代替執行緒的建立
c 11中增加了執行緒,使得我們可以非常方便的建立執行緒,它的基本用法是這樣的 void f int n std thread t f,n 1 t.join std future是乙個非常有用也很有意思的東西,簡單說std future提供了一種訪問非同步操作結果的機制。從字面意思來理解,它表示未來...