1 前言
本文接上文 C++并發(fā)與多線程筆記七:condition_variable、wait、notify_one/all 的內容,主要記錄 async、future、packaged_task、promise 概念以及用法。
2 std::async、std::future 創(chuàng)建后臺任務并返回值
2.1 基本用法
std::async
是個函數(shù)模板,用來啟動一個異步任務,啟動一個異步任務后,它返回一個 std::future
類模板對象。
上述"啟動一個異步任務"的本質含義就是自動創(chuàng)建一個線程并開始執(zhí)行對應的線程入口函數(shù),它返回一個 std::future
類模板對象,這個對象內就含有線程入口函數(shù)的返回值(即線程執(zhí)行的返回結果)。
我們可以通過調用 std::future
對象的成員函數(shù) get()
來獲取結果。即 std::future
提供了一種訪問異步操作結果的機制,就是說這個結果可能沒辦法馬上拿到,在線程執(zhí)行完畢后,才能拿到。
注:
std::future
對象里會保存一個值,在將來的某個時刻能夠拿到。
在下面的示例代碼中,std::future
對象的 get()
成員函數(shù)等待線程結束并返回結果,也就是說 get()
函數(shù)在拿不到值時是阻塞的。另外,std::future
對象還有個 wait()
成員函數(shù),等待線程返回,但函數(shù)本身并不返回結果,類似線程對象中的 join()
函數(shù)。
#include <thread>
#include <future> /* 需要包含該頭文件 */
#include <iostream>
using namespace std;
/* 線程入口函數(shù) */
int myThread() {
cout << "myThread() start thread ID = " << std::this_thread::get_id() << endl;
std::chrono::milliseconds duration(5000); /* 定義間隔時間為 5000 ms */
std::this_thread::sleep_for(duration); /* 睡眠指定的間 */
cout << "myThread() end thread ID = " << std::this_thread::get_id() << endl;
return 5;
}
int main() {
cout << "main() start thread ID = " << std::this_thread::get_id() << endl;
/* 定義一個std::future類模板對象,變量類型為 int, */
/* 并通過 std::async 啟動一個異步任務 myThread */
std::future<int> result = std::async(myThread);
cout << "continue......!" << endl;
/* get() 函數(shù)會阻塞在這,等待 myThread 線程執(zhí)行完畢后返回 */
cout << result.get() << endl;
// result.wait(); /* 等待線程執(zhí)行完畢,但不返回值 */
cout << "main() end thread ID = " << std::this_thread::get_id() << endl;
return 0;
}
輸出結果:
main() start thread ID = 1860
continue......!
myThread() start thread ID = 3300
# 這里會阻塞 5000 ms
myThread() end thread ID = 3300
5
main() end thread ID = 1860
注:
std::future
對象的get()
成員函數(shù)只能調用一次,否則運行時會發(fā)生異常:std::future_error
。
使用類成員函數(shù)作為 std::async
啟動的線程回調函數(shù)有些額外的細節(jié),示例代碼如下,具體詳見注釋:
#include <thread>
#include <future> /* 需要包含該頭文件 */
#include <iostream>
using namespace std;
class A {
public:
/* 線程入口函數(shù) */
int myThread(int myParameter) {
cout << myParameter << endl;
cout << "myThread() start thread ID = " << std::this_thread::get_id() << endl;
std::chrono::milliseconds duration(5000); /* 定義間隔時間為 5000 ms */
std::this_thread::sleep_for(duration); /* 睡眠指定的間 */
cout << "myThread() end thread ID = " << std::this_thread::get_id() << endl;
return 5;
}
};
int main() {
A objA;
int tempParameter = 12;
cout << "main() start thread ID = " << std::this_thread::get_id() << endl;
/* std::async 傳入類成員函數(shù)作為線程回調函數(shù):第二個參數(shù)為類對象引用,第三個參數(shù)開始為線程回調函數(shù)的入?yún)?*/
std::future<int> result = std::async(&A::myThread, &objA, tempParameter);
/* std::async 傳入全局函數(shù)作為線程回調函數(shù):第二個參數(shù)開始為線程回調函數(shù)的入?yún)?*/
// std::future<int> result = std::async(myThread, tempParameter);
cout << "continue......!" << endl;
cout << result.get() << endl;
cout << "main() end thread ID = " << std::this_thread::get_id() << endl;
return 0;
}
如果使用 std::async
啟動異步任務后,不調用 std::future
的 get()
函數(shù)或 wait()
函數(shù),程序在主線程退出前依舊會等待異步任務(子線程)結束,但通常不建議這樣操作,可能會有風險。
2.2 擴展用法
2.2.1 std::launch::deferred
我們可以通過額外向 std::async
傳遞一個 std::launch
類型(枚舉)參數(shù)來達到一些特殊目的。比如 std::launch::deferred
表示線程入口函數(shù)調用被延遲到 std::future
對象的 get()
或者 wait()
函數(shù)被調用時才執(zhí)行。
/*......*/
std::future<int> result = std::async(std::launch::deferred, &A::myThread, &objA, tempParameter);
cout << result.get() << endl;
// result.wait()
/*......*/
輸出結果:
main() start thread ID = 2572
continue......!
12
myThread() start thread ID = 2572
myThread() end thread ID = 2572
5
main() end thread ID = 2572
可以看到,異步任務(線程)中打印的線程 ID 與主線程 ID 一樣,也就是說 std::async
傳入 std::launch::deferred
參數(shù)后,調用 std::future
對象的 get()
或者 wait()
函數(shù)啟動異步任務(線程),實際上并沒有創(chuàng)建新的線程,只起到一個延遲執(zhí)行任務的功能。
注:如果
std::async
傳入std::launch::deferred
參數(shù)后,沒有調用std::future
對象的get()
或者wait()
函數(shù),那么這個異步任務(線程)不會被創(chuàng)建,也不會執(zhí)行。
2.2.2 std::launch::async
std::launch
還有另一個枚舉類型 async
,它是默認參數(shù),表示調用 std::async
函數(shù)時就開始創(chuàng)建線程,并立即開始執(zhí)行,也就是上文一開始講的用法。
3 std::packaged_task
std::packaged_task
是個類模板,它的模板參數(shù)是各種可調用對象。通過 std::packaged_task
來把各種可調用對象包裝起來,方便將來作為線程入口函數(shù)調用,并且 std::packaged_task
包裝的對象中可以拿到線程的 std::future
對象,示例代碼如下:
#include <thread>
#include <future> /* 需要包含該頭文件 */
#include <iostream>
using namespace std;
/* 線程入口函數(shù) */
int myThread(int myParameter) {
cout << myParameter << endl;
cout << "myThread() start thread ID = " << std::this_thread::get_id() << endl;
std::chrono::milliseconds duration(5000); /* 定義間隔時間為 5000 ms */
std::this_thread::sleep_for(duration); /* 睡眠指定的間 */
cout << "myThread() end thread ID = " << std::this_thread::get_id() << endl;
return 5;
}
int main() {
cout << "main() start thread ID = " << std::this_thread::get_id() << endl;
/* 把 myThread 函數(shù)通過 std::packaged_task 包裝起來 */
/* myThread 函數(shù)的返回值和入?yún)⒍际?int 類型,因此模板類型為 int(int) */
std::packaged_task<int(int)> myThreadTask(myThread);
/* 使用包裝好的 myThreadTask 對象,第二個參數(shù)為線程回調函數(shù)的入?yún)?,?chuàng)建線程并開始執(zhí)行 */
std::thread mThreadObj(std::ref(myThreadTask), 12);
mThreadObj.join(); /* 等待線程結束 */
/* 根據(jù) std::packaged_task 包裝的對象獲取 std::future 對象 */
std::future<int> result = myThreadTask.get_future();
cout << result.get() << endl;
cout << "main() end thread ID = " << std::this_thread::get_id() << endl;
return 0;
}
輸出結果:
main() start thread ID = 7512
12
myThread() start thread ID = 3916
myThread() end thread ID = 3916
5
main() end thread ID = 7512
使用 std::packaged_task
包裝 lambda 表達式的示例代碼如下,這個 lambda 表達式的功能與上文的 myThread()
函數(shù)一樣:
/* 把 lambda 表達式通過 std::packaged_task 包裝起來 */
std::packaged_task<int(int)> myThreadTask([](int myParameter) {
cout << myParameter << endl;
cout << "myThread() start thread ID = " << std::this_thread::get_id() << endl;
std::chrono::milliseconds duration(5000); /* 定義間隔時間為 5000 ms */
std::this_thread::sleep_for(duration); /* 睡眠指定的間 */
cout << "myThread() end thread ID = " << std::this_thread::get_id() << endl;
return 5;
});
std::packaged_task
包裝起來的可調用對象是可以直接調用的,所以從這個角度來講,std::packaged_task
對象也是一個可調用對象,比如:
#include <future> /* 需要包含該頭文件 */
#include <iostream>
using namespace std;
int main() {
cout << "main() start thread ID = " << std::this_thread::get_id() << endl;
/* 把 lambda 表達式通過 std::packaged_task 包裝起來 */
std::packaged_task<int(int)> myThreadTask([](int myParameter) {
cout << myParameter << endl;
cout << "myThread() start thread ID = " << std::this_thread::get_id() << endl;
std::chrono::milliseconds duration(5000); /* 定義間隔時間為 5000 ms */
std::this_thread::sleep_for(duration); /* 睡眠指定的間 */
cout << "myThread() end thread ID = " << std::this_thread::get_id() << endl;
return 5;
});
/* 直接執(zhí)行,并獲取函數(shù)返回值 */
myThreadTask(105);
std::future<int> result = myThreadTask.get_future();
cout << result.get() << endl;
cout << "main() end thread ID = " << std::this_thread::get_id() << endl;
return 0;
}
輸出結果:
main() start thread ID = 15236
105
myThread() start thread ID = 15236
myThread() end thread ID = 15236
5
main() end thread ID = 15236
std::packaged_task
可以實現(xiàn)很多靈活的操作,比如創(chuàng)建一個容器,里面可以放入很多 std::packaged_task
對象,需要的時候再拿出來用:
#include <vector>
#include <future> /* 需要包含該頭文件 */
#include <iostream>
using namespace std;
vector<std::packaged_task<int(int)>> myTasks; /* 容器 */
int main() {
cout << "main() start thread ID = " << std::this_thread::get_id() << endl;
/* 把 lambda 表達式通過 std::packaged_task 包裝起來 */
std::packaged_task<int(int)> myThreadTask([](int myParameter) {
cout << myParameter << endl;
cout << "myThread() start thread ID = " << std::this_thread::get_id() << endl;
std::chrono::milliseconds duration(5000); /* 定義間隔時間為 5000 ms */
std::this_thread::sleep_for(duration); /* 睡眠指定的間 */
cout << "myThread() end thread ID = " << std::this_thread::get_id() << endl;
return 5;
});
/* 將 myThreadTask 對象移動到容器中,移動完畢后容器size為1,myThreadTask為空 */
/* 此處只能用 move,不能用 copy,因為 std::packaged_task 的拷貝構造函數(shù)被指定為刪除的(delete),只能移動或者引用,無法拷貝。 */
myTasks.push_back(std::move(myThreadTask));
/* ...... */
/* 將容器中的 std::packaged_task 對象取出來用 */
std::packaged_task<int(int)> myThreadTaskBak;
auto iter = myTasks.begin(); /* 用迭代器獲取容器中的第一個元素 */
myThreadTaskBak = std::move(*iter); /* 同樣用 move 取出來 */
myTasks.erase(iter); /* 刪除容器中的第一個元素,后續(xù)代碼不可以在再使用 iter */
myThreadTaskBak(123); /* 調用拿出來的 std::packaged_task 對象 */
cout << "main() end thread ID = " << std::this_thread::get_id() << endl;
return 0;
}
輸出結果:
main() start thread ID = 13080
123
myThread() start thread ID = 13080
myThread() end thread ID = 13080
main() end thread ID = 13080
4 std::promise
std::promise
也是一個類模板,我們可以在某個線程中給它賦值,然后我們可以在其他線程中把這個值取出來,示例代碼如下:
#include <thread>
#include <future> /* 需要包含該頭文件 */
#include <iostream>
using namespace std;
void myThread(std::promise<int>& promiseValue, int calc) {
int result;
calc++;
calc *= 10;
/* 這里可以做各種運算,假設事件花了 5000 ms */
std::chrono::milliseconds duration(5000); /* 定義間隔時間為 5000 ms */
std::this_thread::sleep_for(duration); /* 睡眠指定的間 */
/* 計算出結果后,將其保存到 std::promise 對象中 */
result = calc;
promiseValue.set_value(result);
return;
}
int main() {
cout << "main() start thread ID = " << std::this_thread::get_id() << endl;
int calc = 128;
/* 聲明一個 std::promise 對象,保存的值類型為 int */
std::promise<int> myPpromiseValue;
/* 創(chuàng)建并執(zhí)行線程函數(shù),等待其結束 */
std::thread myThreadObj(myThread, std::ref(myPpromiseValue), 128);
myThreadObj.join(); /* 用 std::thread 類型的對象,必須用 join 等待線程執(zhí)行完畢 */
/* promise 和 future 綁定,用于獲取線程返回值 */
std::future<int> result = myPpromiseValue.get_future();
cout << result.get() << endl; /* get 只能調用一次 */
cout << "main() end thread ID = " << std::this_thread::get_id() << endl;
return 0;
}
輸出結果:
main() start thread ID = 7520
1290
main() end thread ID = 7520
用法簡單來說就是:通過 std::promise
保存一個值,在將來某個時刻我們通過把一個 std::future
綁定到這個 std::promise
上來得到綁定值。
也可以在上述代碼中加多一個線程,在第二個子線程中獲取第一個子線程設置的 std::promise
對象值,改造后的代碼如下:
#include <thread>
#include <future> /* 需要包含該頭文件 */
#include <iostream>
using namespace std;
void myThread(std::promise<int>& promiseValue, int calc) {
int result;
calc++;
calc *= 10;
/* 這里可以做各種運算,假設事件花了 5000 ms */
std::chrono::milliseconds duration(5000); /* 定義間隔時間為 5000 ms */
std::this_thread::sleep_for(duration); /* 睡眠指定的間 */
/* 計算出結果后,將其保存到 std::promise 對象中 */
result = calc;
promiseValue.set_value(result);
return;
}
void myThreadEx(std::future<int>& futureValue) {
auto result = futureValue.get();
cout << "myThreadEx result = " << result << endl;
return;
}
int main() {
cout << "main() start thread ID = " << std::this_thread::get_id() << endl;
int calc = 128;
/* 聲明一個 std::promise 對象,保存的值類型為 int */
std::promise<int> myPpromiseValue;
/* 創(chuàng)建并執(zhí)行線程函數(shù),等待其結束 */
std::thread myThreadObj(myThread, std::ref(myPpromiseValue), 128);
myThreadObj.join();
/* promise 和 future 綁定,用于獲取線程返回值 */
std::future<int> result = myPpromiseValue.get_future();
/* 創(chuàng)建并執(zhí)行第二個線程,等待其結束 */
std::thread myThreadObjEx(myThreadEx, std::ref(result));
myThreadObjEx.join();
cout << "main() end thread ID = " << std::this_thread::get_id() << endl;
return 0;
}
輸出結果:
main() start thread ID = 2892
myThreadEx result = 1290
main() end thread ID = 2892
std::promise
可以在線程與線程之間傳遞各種類型的數(shù)據(jù)。
5 總結
C++ 并發(fā)與多線程的筆記中介紹了很多庫中帶的類型和函數(shù),但學習這些東西的目的,并不是要把它們都用在自己的實際開發(fā)中,相反,如果能用最少的東西寫出一個穩(wěn)定、高效的多線程程序,這是更好的。代碼寫的優(yōu)雅整潔的最終目的是給人看的,能讓別人輕易看懂并理解的代碼才是好代碼。
我們?yōu)榱顺砷L,必須要閱讀一些高手寫的代碼,從而快速實現(xiàn)自己的代碼積累,等量變發(fā)生質變時,我們的技術會有一個大幅的提升。文章來源:http://www.zghlxwxcb.cn/news/detail-412341.html
此處學習這些內容的目的主要是方便我們未來能夠讀懂高手的代碼,至少看別人代碼,遇到 std::promise
、std::future
等東西時不會一頭霧水。文章來源地址http://www.zghlxwxcb.cn/news/detail-412341.html
到了這里,關于C++并發(fā)與多線程筆記八:async、future、packaged_task、promise的文章就介紹完了。如果您還想了解更多內容,請在右上角搜索TOY模板網以前的文章或繼續(xù)瀏覽下面的相關文章,希望大家以后多多支持TOY模板網!