異步操作簡介
什么是異步操作,為何會有異步操作?
在C++中,不能直接從thread.join()得到結果,必須定義個變量,在線程執行時,對這個變量賦值,然后執行join(),過程相對繁瑣。
Linux中有AIO(異步IO)做異步操作,C++中如何進行異步操作?
答:是有的,C++11提供了異步操作相關的類,主要有std::future, std::promise, std::package_task。
Linux AIO參見Linux 異步IO(AIO)
- std::future 作為異步結果傳輸通道,可以方便獲取線程函數的返回值;
- std::promise 用來包裝一個值,將數據和future綁定起來,方便線程賦值;
- std::package_task 用來包裝一個可調用對象,將函數和future綁定起來,便於異步調用;
頭文件:
std::future
獲取線程返回值的類std::future,用來訪問異步操作的結果。因為異步操作需要時間,future並不能馬上獲取其結果,只能在未來某個時候獲取期待值,因此被稱為future。
可以通過同步等待的方式,通過查詢future的狀態(future_status),來獲取異步操作的結果。
future狀態有3種:
1)Deferred 異步操作還沒開始;
2)Ready 異步操作已經完成;
3)Timeout 異步操作超時;
獲取future異步操作的結果,有4種方式:
1)get 等待異步結束並返回結果;
2)wait 只是等待異步操作結束,沒有返回值;
3)wait_for 超時等待異步操作返回結果;
4)wait_until 等待達到指定的時間點,異步操作返回結果,常配合當前時間點 + 時間段來設置目標時間點;
其同步調用基本用法為:
// 同步查詢future狀態的例子
// 異步求和,同步查詢
int work0(int a, int b) {
this_thread::sleep_for(chrono::seconds(4)); // 通過休眠,模擬異步操作需要一定時間完成
return a + b;
}
std::future_status status;
std::future<int> future;
future = async(work0, 12, 34); // 注意future經常搭配std::async使用,async參見下文
/* 等待、輪詢future狀態 */
do {
status = future.wait_for(chrono::seconds(1)); // 定時獲取future的狀態
if (status == future_status::deferred) { // 異步操作還沒開始
cout << "deferred" << endl;
}
else if (status == future_status::timeout) { // 異步操作超時
cout << "timeout" << endl;
}
else if (status == future_status::ready) { // 異步操作已完成
cout << "ready" << endl;
}
} while (status != future_status::ready);
std::promise
協助線程賦值的類std::promise,將數據和future綁定。在線程函數中,為外面傳入的promise對象賦值,線程函數執行完畢后,就可以通過promise的future獲取該值。
注意:future是定義在promise內部的成員。
get_future函數
函數返回一個與promise共享狀態相關聯的future對象。返回的future對象可以訪問由promise對象設置在共享狀態上的值或者某個異常對象。
promise對象通常會在某個時間點准備好(設置一個值或者一個異常對象),然后在另一個線程中,用future對象的get獲取值。
future<_Ty> get_future()
set_value函數
設置共享狀態值,此后promise的共享狀態標志變為ready
void set_value(const _Ty& _Val)
set_exception函數
為promise設置異常,此后promise的共享狀態標志變為ready
void set_exception(_XSTD exception_ptr _Exc)
promise基本用法
promise<int> pr;
future<int> f = pr.get_future();
thread t([](promise<int> &p) { p.set_value_at_thread_exit(9); }, ref(pr)); // lambda是匿名函數,ref(pr)是該函數實參
t.detach();
try {
auto r = f.get();
cout << "get future from promise: " << r << endl;
}
catch (const exception& e) {
cout << "exception: " << e.what() << endl;
}
可以將上面線程函數由lambda改成使用普通的線程函數:
thread t([](promise<int> &p) { p.set_value_at_thread_exit(9); }, ref(pr));
// <=>
thread t(work1, ref(pr));
void work1(promise<int> &pr) {
this_thread::sleep_for(chrono::seconds(3));
#if 0
try {
throw runtime_error("Runtime error");
}
catch (...) {
pr.set_exception(current_exception());
}
#else
pr.set_value(9);
#endif
}
promise有set_value和set_value_at_thread_exit,都可以用來對promise賦值,區別在於后者要求賦值的promise對象不能被銷毀(如不能使用右值傳值,然后在內部用std::move構造臨時promise對象),而前者沒有這個要求。
參見關於std::promise的set_value_at_thread_exit | CSDN
std::package_task
可調用對象的包裝類std::package_task,包裝了一個可調用的目標類(如function, lambda expression, bind expression, another function object),將其與future綁定,以便異步調用。
promise和package_task類似,promise保存了一個共享狀態的值,package_task保存的是一個可調用對象(如函數)。
package_task基本用法
packaged_task<int()> task([]() {return 7; }); // 包裝可調用對象(lambda expression)
thread t2(ref(task));
future<int> f1 = task.get_future();
auto r1 = f1.get();
t2.detach();
cout << r1 << endl;
std::promise, std::package_task, std::future三者關系
future提供異步操作結果訪問機制,跟線程一個級別,屬於低層次對象。
promise和package_task是在future的高一層,內部封裝了future以便訪問異步操作結果。promise內部包裝的是一個值,package_task內部包裝的是一個可調用對象。當需要線程中的某個值時,使用promise;當需要獲得一個異步操作的返回值時,使用package_task。
簡單來說,package_task會自動將異步操作的結果保存到promise對象中,而promise需要在線程函數中手動設置promise值。
另外,可以用future保存異步操作結果,但future的copy函數是禁用的,只能使用move函數。而shared_future是可以copy的。因此,當要把future保存到容器中時,請使用shared_future。
package_task和shared_future基本用法
int func(int x) {
return x + 2;
}
packaged_task<int(int)> task(func);
future<int> ft = task.get_future();
thread(std::move(task), 2).detach(); // task作為線程函數
int res = ft.get();
cout << res << endl; // 打印4
// future不可copy,無法放入容器。要放入容器,使用shared_future
vector<shared_future<int>> vec;
auto f = std::async(launch::async, [](int a, int b) { return a + b; }, 2, 3).share(); // 注意這里的share()將future轉化為shared_future
vec.push_back(f);
cout << "The shared_future result is " << vec[0].get() << endl; // 打印5
std::async
線程異步操作函數std::async,比promise, package_task, thread更高一層,可用來創建異步task,其返回結果保存到future對象中。當需要取得異步結果時,調用future.get()即可。如果不關注結果,只是簡單等任務完成,可以使用future.wait()
async原型:
async(std::launch::async | std::launch::deferred, f, args,...);
-
第一個參數是線程的創建策略,默認是std::launch::async。
1)std::launch::async 調用async時就開始創建線程;
2)std::launch::deferred 延遲加載方式創建線程。調用async時不創建線程,直到調用了future的get或者wait,才創建線程。 -
第二個參數是線程函數
-
第三個參數是傳入線程函數的參數
async使用示例
// 使用launch::async策略創建線程,並阻塞get其值
future<int> f1 = async(launch::async, []() {return 8; });
cout << f1.get() << endl; // 打印8
// 使用launch::async策略創建線程,並輪詢future狀態
future<int> f2 = async(launch::async, []() {
this_thread::sleep_for(chrono::seconds(3));
return 8; });
cout << "waiting..." << endl;
future_status status;
do {
status = f2.wait_for(chrono::seconds(1));
if (status == future_status::deferred) { // 異步操作尚未完成
cout << "deferred" << endl;
}
else if (status == future_status::timeout) { // 異步操作超時
cout << "timeout" << endl;
}
else if (status == future_status::ready) {
cout << "ready!" << endl;
}
} while (status != future_status::ready);
cout << "result is " << f2.get() << endl;
// 使用launch::deferred策略創建線程,並輪詢future狀態
future<int> f3 = async(launch::deferred, []() { return 3; });
future_status status3;
do {
status3 = f3.wait_for(chrono::seconds(1));
if (status3 == future_status::deferred) {
cout << "deferred" << endl;
}
} while (status3 != future_status::ready);
參考
[1]祁宇. 深入應用C++11 : 代碼優化與工程級應用 : In-Depth C++11 : code optimization and engineering level application[M]. 機械工業出版社, 2015.