多執行緒07:async、future、packaged_task、promise
📕async、future、packaged_task、promise
本節內容需要包含頭文件:#include <future>
一、std::async、 std::future 創建後台任務並返回值
①:啟用async
- std::async是一個函數模板,用來啟動一個非同步任務,啟動起來一個非同步任務之後,它返回一個std::future對象,這個對象是個類模板。
- 「啟動一個非同步任務」:就是自動創建一個執行緒,並開始 執行對應的執行緒入口函數,它返回一個std::future對象,這個std::future對象中就含有執行緒入口函數所返回的結果,我們可以通過調用future對象的成員函數get()來獲取結果。
- 「future」將來的意思,也有人稱呼std::future提供了一種訪問非同步操作結果的機制,就是說這個結果你可能沒辦法馬上拿到,但是在不久的將來,這個執行緒執行完畢的時候,你就能夠拿到結果了,所以,大家這麼理解:future中保存著一個值,這個值是在將來的某個時刻能夠拿到。
- std::future對象的get()成員函數會等待執行緒執行結束並返回結果,拿不到結果它就會一直等待,感覺有點像join()。但是,它是可以獲取結果的。並且記得一個future只能調用一次get(無論哪裡,都會將數據轉移),調用兩次程式會崩潰。
- std::future對象的wait()成員函數,用於等待執行緒返回,本身並不返回結果,這個效果和 std::thread 的join()更像。沒有隻能調用一次限制,但是調用多也沒有效果。
- 如果不調用future的get()成員函數和wait()成員函數,程式也會在主函數return處一直等待調用子執行緒結束;
看例子:
#include <iostream>
#include <future>
using namespace std;
class A {
public:
int mythread(int data) {
cout << data << endl;
return data * 2;
}
};
int mythread() {
cout << "mythread() start" << "threadid = " << std::this_thread::get_id() << endl;
std::chrono::milliseconds dura(5000);//睡5秒
std::this_thread::sleep_for(dura);
cout << "mythread() end" << "threadid = " << std::this_thread::get_id() << endl;
return 5;
}
int main() {
A a;
int tmp = 12;
cout << "main" << "threadid = " << std::this_thread::get_id() << endl;
std::future<int> result1 = std::async(mythread);
cout << "continue........" << endl;
cout << result1.get() << endl; //卡在這裡等待mythread()執行完畢,拿到結果
//類成員函數
std::future<int> result2 = std::async(&A::mythread, &a, tmp); //第二個參數是對象引用才能保證執行緒里執行的是同一個對象
//cout << result2.get() << endl;//get()只能調用一次,result2不再有結果值,轉移的是存的數據類型,這裡是int!
//或者result2.wait();
cout << "finish all" << endl;
return 0;
}
②:std::async的第一個參數
- 參數:std::launch::deferred: 表示執行緒入口函數調用被延遲到std::future的wait()或者get()函數調用時才執行; 那如果wait()或者get()沒調用,那麼執行緒沒執行!執行緒沒有創建!是在主執行緒中調用的執行緒入口函數!
#include <iostream>
#include <future>
using namespace std;
int mythread() {
cout << "mythread() start" << "threadid = " << std::this_thread::get_id() << endl;
std::chrono::milliseconds dura(5000);
std::this_thread::sleep_for(dura);
cout << "mythread() end" << "threadid = " << std::this_thread::get_id() << endl;
return 5;
}
int main() {
cout << "main" << "threadid = " << std::this_thread::get_id() << endl;
std::future<int> result1 = std::async(std::launch::deferred ,mythread);
cout << "continue........" << endl;
cout << result1.get() << endl; //卡在這裡等待mythread()執行完畢,拿到結果
cout << "finish all" << endl;
return 0;
}
- 參數:std::launch::async:在調用async函數的時候就開始創建執行緒;會強制std::async創建新執行緒,和thread一樣。
- 帶上兩個參數:std::launch::async |std::launch::deferred 這裡這個 |:以為這調用async的行為可能是 創建新執行緒並立即執行,或者沒有創建新執行緒並且延遲調用result.get()才開始執行任務入口函數,兩者居其一。(系統會自行決定是非同步(創建新執行緒)還是同步(不創建新執行緒)方式運行),默認值就是這個!
二、std::package_task
- 目的就是:打包任務,把任務包裝起來
- std::packaged_task是個模板類,它的模板參數是各種可調用對象(函數,函數指針,lambda表達式,bind創建的對象,以及重載了函數調用符的類,有點像function);通過std::packaged_task來把各種可調用對象包裝起來,方便將來作為執行緒入口函數來調用。
- packaged_task包裝起來的可調用對象還可以直接調用,所以從這個角度來講,packaged_task對象,也是一個可調用對象。
- 看例子:
//可調用對象是普通函數
#include <thread>
#include <iostream>
#include <future>
using namespace std;
int mythread(int mypar) {
cout << mypar << endl;
cout << "mythread() start" << "threadid = " << std::this_thread::get_id() << endl;
std::chrono::milliseconds dura(5000);
std::this_thread::sleep_for(dura);
cout << "mythread() end" << "threadid = " << std::this_thread::get_id() << endl;
return 5;
}
int main() {
cout << "main" << "threadid = " << std::this_thread::get_id() << endl;
//我們把函數mythread通過packaged_task包裝起來
//參數是一個int,返回值類型是int
std::packaged_task<int(int)> mypt(mythread);
std::thread t1(std::ref(mypt), 1);//傳入真引用!不可以用detach模式
t1.join();
std::future<int> result = mypt.get_future();
//std::future對象里包含有執行緒入口函數的返回結果,這裡result保存mythread返回的結果。
cout << result.get() << endl;
return 0;
}
//可調用對象是lambda表達式
int main() {
cout << "main" << "threadid = " << std::this_thread::get_id() << endl;
std::packaged_task<int(int)> mypt([](int mypar) {
cout << mypar << endl;
cout << "mythread() start" << " threadid = " << std::this_thread::get_id() << endl;
std::chrono::milliseconds dura(5000);
std::this_thread::sleep_for(dura);
cout << "mythread() end" << " threadid = " << std::this_thread::get_id() << endl;
return 5;
});
std::thread t1(std::ref(mypt), 1);//傳真引用,因為是packege_task,mypt只能再被調用一次
t1.join();
std::future<int> result = mypt.get_future();
cout << result.get() << endl;
cout << "finish overhead" << endl;
return 0;
}
//調用對象是lambda表達式,直接調用
int main() {
cout << "main" << "threadid = " << std::this_thread::get_id() << endl;
std::packaged_task<int(int)> mypt([](int mypar) {
cout << mypar << endl;
cout << "mythread() start" << " threadid = " << std::this_thread::get_id() << endl;
std::chrono::milliseconds dura(5000);
std::this_thread::sleep_for(dura);
cout << "mythread() end" << " threadid = " << std::this_thread::get_id() << endl;
return 5;
});
mypt(1);//也可以在主執行緒中直接調用,這樣就和function的效果一樣,但是只能調用一次
std::future<int> result = mypt.get_future();
cout << result.get() << endl;
cout << "finish overhead" << endl;
return 0;
}
tip:了解ref
三、promise
-
std::promise 類模板,我們能夠在某個執行緒中給它賦值,然後我們可以在其他執行緒中把這個值取出來用;
-
總結:通過promise保存一個值,在將來某時刻我們通過把一個future綁定到這個promise上來得到這個綁定的值;
#include<iostream>
#include<thread>
#include<mutex>
#include<future>
using namespace std;
void mythread(std::promise<int>&tmpp, int calc)
{
//做一系列複雜的操作
calc++;
calc *= 10;
//做其他運算,比如整整花費了5秒鐘
std::chrono::milliseconds dura(5000); //定一個5秒的時間
std::this_thread::sleep_for(dura); //休息一定時常
int result = calc; //保存結果
tmpp.set_value(result); //####1.結果保存到了tmpp這個promise對象中
}
void mythread2(std::future<int> &tmpf)
{
auto result = tmpf.get();
cout <<"mythread result = " << result<<endl;
}
int main()
{
std::promise<int> myprom; //聲明一個std::promise對象myprom,保存的值類型為int;
std::thread t1(mythread,std::ref(myprom),180);
t1.join();
//獲取結果值
std::future<int> fu1 = myprom.get_future();//####2.promise與future綁定,用於獲取執行緒返回值
std::thread t2(mythread2,std::ref(fu1));
t2.join(); //等mythread2執行完畢
cout << "finish all" << endl;
//fu1不再有用,已經為空;已經全部傳入mythread2的tmpf中,但是myprom還存著值1810;
return 0;
}
//對於ref,package_task、future都會移動後原本的變數就沒有內容了,對於promise,卻還有,要探索一下
四、std::asyn深入理解
4.1 std::async參數詳述
-
延遲調用參數 std::launch::deferred【延遲調用】,std::launch::async【強制創建一個執行緒】;
-
std::async()我們一般不叫創建執行緒(他能夠創建執行緒),我們一般叫它創建一個非同步任務。std::async和std::thread最明顯的不同:就是 async 有時候並不創建新執行緒。
①如果用std::launch::deferred 來調用async?
延遲到調用 get() 或者 wait() 時執行,如果不調用就不會執行
②如果用std::launch::async來調用async?
強制這個非同步任務在新執行緒上執行,這意味著,系統必須要創建出新執行緒來運行入口函數。
③如果同時用 std::launch::async | std::launch::deferred
這裡這個 | 意味著async的行為可能是 std::launch::async 創建新執行緒立即執行, 也可能是 std::launch::deferred 沒有創建新執行緒並且延遲到調用get()執行,由系統根據實際情況來決定採取哪種方案
④不帶額外參數 std::async(mythread),只給async 一個入口函數名,此時的系統給的默認值是 std::launch::async | std::launch::deferred 和 ③ 一樣,有系統自行決定非同步還是同步運行。
4.2 std::async和std::thread()區別
- std::thread()如果系統資源緊張可能出現創建執行緒失敗的情況,如果創建執行緒失敗那麼程式就可能崩潰,而且不容易拿到函數返回值(不是拿不到)
- std::async()創建非同步任務。可能創建執行緒也可能不創建執行緒,並且容易拿到執行緒入口函數的返回值;
由於系統資源限制:
①如果用std::thread創建的執行緒太多,則可能創建失敗,系統報告異常,崩潰。
②如果用std::async,一般就不會報異常,因為如果系統資源緊張,無法創建新執行緒的時候,async不加額外參數的調用方式就不會創建新執行緒。而是在後續調用get()請求結果時執行在這個調用get()的執行緒上。
如果你強制async一定要創建新執行緒就要使用 std::launch::async 標記。承受的代價是,系統資源緊張時可能崩潰。
③根據經驗,一個程式中執行緒數量 不宜超過100~200 。
4.3 async不確定性問題的解決
讓系統自行決定是否創建執行緒:std::future<int> result = std::async(mythread);
問題焦點在於這個寫法,任務到底有沒有被推遲執行!
如何判斷:通過future中wait_for()方法的返回值;詳情看下一節內容
多閱讀高手程式碼,多積累,提升自己技術!