問題1: 如何理解線程異步?
?異步的反義詞是同步。異步與同步的區(qū)別見此處: [[Linux/計(jì)算機(jī)網(wǎng)絡(luò)基礎(chǔ)知識點(diǎn)/高級IO#同步通信 vs 異步通信]]。實(shí)際上在多線程下,大部分時(shí)候都是存在過異步這一狀態(tài)的。主線程在創(chuàng)建了子線程后,也去干自己的任務(wù)了。
問題2: 線程異步的應(yīng)用場景?
future
對象進(jìn)行存儲(chǔ)。(future是個(gè)模板類, 能存儲(chǔ)任意類型, 包括void)//子—>主?
2. future//包含于頭文件
templatefuture;
templatefuture; // specialization : T is a reference type (R&)
template<> future; // specialization : T is void
//---------------------------------------------------
//構(gòu)造函數(shù)
future() noexcept; //(1) default
future (const future&) = delete; //(2) copy [deleted]
future (future&& x) noexcept; //(3) move
//賦值
future& operator=(future&& other) noexcept;
future& operator=(const future& other) = delete;
?
?future對象是用于存儲(chǔ)某一類型
的值的,只不過這個(gè)值往往是在未來才能獲取到。 它被用來作為線程異步的中間存儲(chǔ)值。future的值由以下三個(gè)異步任務(wù)的提供者(Provider)提供:
std::promise
std::package_task
std::async
?我們根據(jù)future的構(gòu)造函數(shù)可以發(fā)現(xiàn),future不支持拷貝構(gòu)造。future的operator=()
會(huì)去調(diào)用移動(dòng)構(gòu)造。
?
2.1 共享狀態(tài)?future在線程異步當(dāng)中扮演的是一個(gè)被動(dòng)角色。它需要與promise
、package_task
、async
配合來實(shí)現(xiàn)線程異步。由于它必須要進(jìn)行共享關(guān)聯(lián),因此future對象時(shí)存在共享狀態(tài)是否有效的問題的。只有共享狀態(tài)有效,才能獲取future的值。
?future對象是有"共享狀態(tài)"這一概念的。共享狀態(tài)必須依靠上面提到的三者對應(yīng)的方法:promise::get_future()
、package_task::get_future()
、async()
獲取。否則單純的創(chuàng)建一個(gè)future對象, 它的共享狀態(tài)是無效的!
共享狀態(tài) | 解釋 |
---|---|
future_status::deferred | 子線程中的任務(wù)函仍未啟動(dòng) |
future_status::timeout | 子線程中的任務(wù)正在執(zhí)行中,指定等待時(shí)長已用完 |
future_status::ready | 子線程中的任務(wù)已經(jīng)執(zhí)行完畢,結(jié)果已就緒 |
?實(shí)際上,為了方便我們理解,還應(yīng)該加一個(gè)狀態(tài): 無效狀態(tài)(invalid)。這個(gè)狀態(tài)存在于:
①future對象沒有接收任何提供者的共享關(guān)聯(lián);
②future對象ready完畢后,被調(diào)用者通過get()
獲取過了。
?
2.2 常用成員函數(shù)成員函數(shù) | 功能 |
---|---|
valid() | 判斷共享狀態(tài)是否有效 |
wait() | 等待共享狀態(tài)ready |
wait_for() | 等待一段時(shí)間 |
wait_until() | 等待到某個(gè)時(shí)間點(diǎn) |
get() | 獲取future的值 |
注意:
get()
時(shí),如果future的共享狀態(tài)不是ready, 則調(diào)用者會(huì)被阻塞。get()
只能被調(diào)用一次,第二次會(huì)拋出異常。(因?yàn)榈谝淮瓮瓿珊?,future的狀態(tài)就是無效的了)wait()
方法會(huì)阻塞式等待共享狀態(tài)為ready。wait_for()
和wait_until()
無法保證等待結(jié)束后的future對象的狀態(tài)一定是ready! (所以它們不太常用, 因?yàn)檎{(diào)用完畢后還需要使用valid()
判斷共享狀態(tài))wait_for()
和wait_until()
的返回值是std::future_status
。因此我們可以通過接收它們的返回值來循環(huán)判斷future對象是否ready。?
3. promise//包含于頭文件
templatepromise;
templatepromise; // specialization : T is a reference type (R&)
template<> promise;// specialization : T is void
//構(gòu)造函數(shù)
promise(); //(1)
promise(promise&& other) noexcept; //(2) 移動(dòng)構(gòu)造
promise(const promise& other) = delete; //(3) 禁止拷貝構(gòu)造
//賦值
promise& operator= (promise&& rhs) noexcept; //允許移動(dòng)賦值
promise& operator= (const promise&) = delete;//禁止拷貝賦值
?promise是一個(gè)協(xié)助線程賦值的類,在promise類的內(nèi)部管理著一個(gè)future對象。因此它能夠提供一些將數(shù)據(jù)和future對象綁定起來的接口。
?
3.1 常用成員函數(shù)成員函數(shù) | 功能 |
---|---|
get_future() | 獲取future對象 |
set_value() | 設(shè)置future對象的值(立刻) |
set_value_at_thread_exit() | 在線程結(jié)束時(shí),才會(huì)設(shè)置future對象的值, |
?
? get_future()
?get_future()會(huì)返回一個(gè)future對象, 此時(shí)如果去接收它的返回值則會(huì)觸發(fā)移動(dòng)賦值, 將資源轉(zhuǎn)移。
? set_value()
?設(shè)置future對象的值,并立即設(shè)置future對象的共享狀態(tài)為ready
。
? set_value_at_thread_exit()
?設(shè)置future對象的值,但是不會(huì)立刻讓future對象的共享狀態(tài)為ready
。在子線程退出時(shí),子線程資源被銷毀,再令共享狀態(tài)為ready
。
?
3.2 promise的基本使用①: 子線程set_value
—>給主線程
promise
對象promise
對象通過引用傳遞的方式傳給子線程的任務(wù)函數(shù)(ref
)set_value()
方法, 設(shè)置future對象的值以及狀態(tài)(ready)promise
對象中的get_future()
方法獲取到future對象 (這里是移動(dòng)構(gòu)造了)future
對象中的get()
方法獲取到子線程set_value()
所設(shè)置的值。void func(promise& pr)
{cout<< "Child Thread Working~~~"<< endl;
cout<< "Child Thread: Waiting 3 seconds!"<< endl;
this_thread::sleep_for(chrono::seconds(3));
pr.set_value(3);
this_thread::sleep_for(chrono::seconds(1));
cout<< "Child Exit"<< endl;
}
int main()
{promisepr;
thread t(func, ref(pr));
auto f = pr.get_future();
this_thread::sleep_for(chrono::seconds(1));
cout<< "Get Future: "<< f.get()<< endl;
t.join();
return 0;
}
注意:
?根據(jù)現(xiàn)象, 我們可以發(fā)現(xiàn)主線程在調(diào)用f.get()
時(shí)阻塞了一會(huì)。此時(shí)說明子線程還沒有執(zhí)行到set_value()
, 此時(shí)的future
對象中的共享狀態(tài)不是ready
, 因此主線程會(huì)被阻塞。
②: 主線程set_value
–>給子線程
promise
對象promise
對象通過引用傳遞的方式傳給子線程的任務(wù)函數(shù)(ref
)set_value()
方法, 設(shè)置future對象的值以及狀態(tài)(ready)future
對象的值的判斷條件,當(dāng)future
的共享狀態(tài)或者值滿足條件時(shí),執(zhí)行某一任務(wù)(或終止)void func2(promise& pr)
{int i = 0;
auto val = pr.get_future().get();
if(val == 1){cout<< "Get Value: "<< val<< endl;
//do something
}
else{cout<< "Get Value: "<< val<< endl;
//do something
}
}
int main()
{promisepr;
thread t(func2, ref(pr));
cout<< "Main Thread: Waiting 3 seconds!"<< endl;
this_thread::sleep_for(chrono::seconds(3));
pr.set_value(1);
t.join();
}
輸出:
Main Thread: Waiting 3 seconds!
Get Value: 1
?
?
4. package_task//包含于頭文件
templatepackaged_task; // undefined
templateclass packaged_task;
//構(gòu)造函數(shù)
packaged_task() noexcept; //default (1)
templateexplicit packaged_task (Fn&& fn); //initialization (2)
packaged_task (const packaged_task&) = delete; //copy [deleted] (3)
packaged_task (packaged_task&& x) noexcept; //move (4)
//賦值
packaged_task& operator=(packaged_task&& rhs) noexcept; //move (1)
packaged_task& operator=(const packaged_task&) = delete;//copy [deleted] (2)
?package_task包裝了一個(gè)函數(shù)對象(類似于function), 我們可以把它當(dāng)做函數(shù)對象來使用。package_task可以將內(nèi)部包裝的函數(shù)和future綁定到一起,以便于進(jìn)行后續(xù)的異步調(diào)用。因此我們可以將其理解為它自帶了一個(gè)函數(shù),并且該函數(shù)和future對象綁定到了一起,我們不需要額外定義函數(shù)方法了,直接實(shí)現(xiàn)package_task中的函數(shù)對象即可。
?但package_task相比于promise有個(gè)缺點(diǎn),它里面包裝了函數(shù),而該函數(shù)的返回值就是future對象的值。它無法像使用promise那樣靈活。
?
4.1 常用成員函數(shù)?package_task中最常用的就是get_future()
方法了。它能夠獲取到package_task中的future對象。
?
4.2 package_task的基本使用?將package_task作為線程的啟動(dòng)函數(shù)傳過去,傳參方式必須是引用傳遞 ref()。
int main()
{packaged_taskpt_Add([](int x, int y)
{cout<< "Running~~~~~~~~~~"<< endl;
this_thread::sleep_for(chrono::seconds(3));
return x + y;
});
futurefi = pt_Add.get_future();
cout<< "Start Thread!"<< endl;
thread t(ref(pt_Add), 10, 20);
cout<< "before get"<< endl;
int val = fi.get();
cout<< "val: "<< val<< endl;
t.join();
return 0;
}
輸出:
Start Thread!
before get
Running~~~~~~~~~~
val: 30
?
?
5. async//構(gòu)造函數(shù)
// (1)
templatefuture::type>async (Fn&& fn, Args&&... args);
// (2)
templatefuture::type>async (launch policy, Fn&& fn, Args&&... args); //policy是啟動(dòng)策略
?async是一個(gè)函數(shù),它相比于前面的promise和package_task要高級一些。async可以直接啟動(dòng)一個(gè)子線程,然后讓這個(gè)子線程執(zhí)行對應(yīng)的任務(wù)函數(shù),任務(wù)函數(shù)的返回值就會(huì)被存儲(chǔ)到future對象當(dāng)中,future對象也就是async函數(shù)的返回值。主線程只需要接收asnyc的返回值,然后調(diào)用get()
方法即可獲取到future對象中保存的值。
注: 更高級并不代表更好用,只是它的集成度更高一些,省去了我們要自己創(chuàng)建線程的步驟。async仍然有package_task的缺點(diǎn),它無法像promise那樣自由控制future在何時(shí)賦值。
?
? launch policy啟動(dòng)策略
策略 | 解釋 |
---|---|
std::launch::async | 調(diào)用async函數(shù)時(shí)會(huì)創(chuàng)建新的線程,讓該線程執(zhí)行任務(wù)函數(shù) |
std::launch::deferred | 調(diào)用async函數(shù)時(shí)不會(huì)創(chuàng)建新的線程,也不會(huì)去執(zhí)行該任務(wù)函數(shù)。只有調(diào)用了async返回的future的get() 方法或者wait() 方法時(shí)才會(huì)去執(zhí)行任務(wù)。(執(zhí)行該任務(wù)的是主線程) |
?
5.1 async的基本使用? 使用默認(rèn)的啟動(dòng)策略 — 調(diào)用async創(chuàng)建子線程, 并讓該線程去執(zhí)行任務(wù)
int main()
{futuref = async([](int x, int y)
{ cout<< "Child Thread: Waiting 3 seconds!"<< endl;
this_thread::sleep_for(chrono::seconds(3));
return x + y;
}, 10, 20);
this_thread::sleep_for(chrono::seconds(1));
cout<< "Get Value: "<< f.get()<< endl;
return 0;
}
輸出:
Child Thread: Waiting 3 seconds!
Get Value: 30
? 使用deferred啟動(dòng)策略 — 調(diào)用async不創(chuàng)建子線程
int main()
{futuref = async(launch::deferred, [](int x, int y)
{cout<< "Child Thread "<< this_thread::get_id()<< ": Waiting 3 seconds!"<< endl;
this_thread::sleep_for(chrono::seconds(3));
return x + y;
}, 10, 20);
cout<< "Main Thread "<< this_thread::get_id()<<": Working!!!"<< endl;
auto val = f.get();
cout<< "Get Value: "<< val<< endl;
return 0;
}
輸出:
Main Thread 1: Working!!!
Child Thread 1: Waiting 3 seconds!
Get Value: 30
?我們可以發(fā)現(xiàn)使用deferred策略時(shí),是不會(huì)創(chuàng)建新的線程的。也就是說async的任務(wù)函數(shù)依然是由主線程自己去執(zhí)行的,只不過執(zhí)行的時(shí)機(jī)可以控制 (在調(diào)用get()
方法時(shí)會(huì)去執(zhí)行),這個(gè)機(jī)制類似于回調(diào)函數(shù),你主動(dòng)去調(diào)用get()
才會(huì)去回調(diào)執(zhí)行async的任務(wù)。
?
?
6. promise、package_task、async的對比與總結(jié)?
細(xì)節(jié)總結(jié):
get_future()
方法, 并不會(huì)讓線程被阻塞。只要調(diào)用future對象的get()
方法,才可能被阻塞。(future共享狀態(tài)沒有ready就會(huì)被阻塞, 前提是future共享狀態(tài)有效)ref()
的時(shí)機(jī)。你是否還在尋找穩(wěn)定的海外服務(wù)器提供商?創(chuàng)新互聯(lián)www.cdcxhl.cn海外機(jī)房具備T級流量清洗系統(tǒng)配攻擊溯源,準(zhǔn)確流量調(diào)度確保服務(wù)器高可用性,企業(yè)級服務(wù)器適合批量采購,新人活動(dòng)首月15元起,快前往官網(wǎng)查看詳情吧