深入了解C++11中promise和future的使用
Promise和Future
原理
C++11中promise和future機(jī)制是用于并發(fā)編程的一種解決方案,用于在不同線程完成數(shù)據(jù)傳遞(異步操作)。
傳統(tǒng)方式通過(guò)回調(diào)函數(shù)處理異步返回的結(jié)果,導(dǎo)致代碼邏輯分散且難以維護(hù)。
Promise和Future是一種提供訪問(wèn)異步操作結(jié)果的機(jī)制,可以在線程之間傳遞數(shù)據(jù)和異常消息。
應(yīng)用場(chǎng)景:顧客在一家奶茶店點(diǎn)了單,服務(wù)員給顧客一個(gè)單號(hào),當(dāng)奶茶做好后,服務(wù)員更新排號(hào)的狀態(tài),顧客可以去做自己的事情了,顧客可以通過(guò)查詢排號(hào)來(lái)得知奶茶是否做好,當(dāng)查到奶茶做好了就可以回來(lái)取奶茶了。
#include <iostream> #include <future> #include <thread> using namespace std; using namespace std::chrono; void WaitForMilkTea(future<int>& future) { /*其中獲取future結(jié)果有三種方式 1、auto value = future.get() get()方法會(huì)阻塞等待異步操作結(jié)束并返回結(jié)果 2、std::future_status 方式判斷狀態(tài) 有deferred、timeout、ready三種狀態(tài) 3、可以 */ //future_status方法 #if 0 std::future_status status; do { status = future.wait_for(std::chrono::milliseconds(500)); if (status == std::future_status::deferred) { std::cout << "deferred!!!" << std::endl; //異步操作還沒(méi)開(kāi)始 } else if (status == std::future_status::timeout) { std::cout << "timeout!!!" << std::endl; //異步操作超時(shí) } else if (status == std::future_status::ready) { std::cout << "ready!!!" << std::endl; //異步操作已經(jīng)完成 } } while (status != std::future_status::ready); //通過(guò)判斷future_status狀態(tài)為ready時(shí)才通過(guò)get()獲取值 auto notice = future.get(); std::cout << "WaitForMilkTea receive value:" << notice << std::endl; #endif //get()方法 #if 0 auto notice = future.get(); //get阻塞等待直到異步處理結(jié)束返回值 std::cout << "WaitForMilkTea receive value:" << notice << std::endl; #endif //wait()方法 future.wait(); //和get()區(qū)別是wait只等待異步操作完成,沒(méi)有返回值 auto notice = future.get(); std::cout << "WaitForMilkTea receive value:" << notice << std::endl; } void MakeTea(promise<int>& promise) { //do something 這里先睡眠5s std::this_thread::sleep_for(std::chrono::seconds(5)); promise.set_value(1); std::this_thread::sleep_for(std::chrono::seconds(10)); std::cout << "MakeTea Thread quit!!!" << std::endl; } int main() { promise<int> pNotice; //獲取與promise相關(guān)聯(lián)的future future<int> pFuture = pNotice.get_future(); thread Customer(WaitForMilkTea, ref(pFuture)); thread Worker(MakeTea, ref(pNotice)); Customer.join(); Worker.join(); }
其中future_status枚舉如下:
名稱 | 值 | 示意 |
---|---|---|
ready | 0 | 就緒 |
timeout | 1 | 等待超時(shí) |
deferred | 2 | 延遲執(zhí)行(與std::async配合使用) |
future_errc 枚舉 : 為 future_error 類報(bào)告的所有錯(cuò)誤提供符號(hào)名稱。
名稱 | 值 | 示意 |
---|---|---|
broken_promise | 0 | 與其關(guān)聯(lián)的 std::promise 生命周期提前結(jié)束 |
future_already_retrieved | 1 | 重復(fù)調(diào)用 get() 函數(shù) |
promise_already_satisfied | 2 | 與其關(guān)聯(lián)的 std::promise 重復(fù) set |
no_state | 4 | 無(wú)共享狀態(tài) |
Promise和Future模型
流程如下:
1.線程1初始化一個(gè)promise和future對(duì)象,將promise對(duì)象傳遞給線程2,相當(dāng)于線程2對(duì)線程1的一個(gè)承諾
2.future相當(dāng)于一個(gè)承諾,用于獲取未來(lái)線程2的值
3.線程2接受一個(gè)promise,需要將處理結(jié)果通過(guò)promise返回給線程1
4.線程1想要獲取數(shù)據(jù),此時(shí)線程2還未返回promise就阻塞等待處,直到線程2的數(shù)據(jù)可達(dá)
promise相關(guān)函數(shù)
std::future負(fù)責(zé)訪問(wèn), std::future是一個(gè)模板類,它提供了可供訪問(wèn)異步執(zhí)行結(jié)果的一種方式。
std::promise負(fù)責(zé)存儲(chǔ), std::promise也是一個(gè)模板類,它提供了存儲(chǔ)異步執(zhí)行結(jié)果的值和異常的一種方式。
總結(jié):std::future負(fù)責(zé)訪問(wèn),std::promise負(fù)責(zé)存儲(chǔ),同時(shí)promise是future的管理者
std::future
名稱 | 作用 |
---|---|
operator= | 移動(dòng) future 對(duì)象,移動(dòng)! |
share() | 返回一個(gè)可在多個(gè)線程中共享的 std::shared_future 對(duì)象 |
get() | 獲取值(類型由模板類型決定) |
valid() | 檢查 future 是否處于被使用狀態(tài),也就是它被首次在首次調(diào)用 get() 或 share() 前。建議使用前加上valid()判斷 |
wait() | 阻塞等待調(diào)用它的線程到共享值成功返回 |
wait_for() | 在規(guī)定時(shí)間內(nèi) 阻塞等待調(diào)用它的線程到共享值成功返回 |
wait_until() | 在指定時(shí)間節(jié)點(diǎn)內(nèi) 阻塞等待調(diào)用它的線程到共享值成功返回 |
1、普通構(gòu)造函數(shù), 默認(rèn)無(wú)參構(gòu)造函數(shù)
2、帶自定義內(nèi)存分配器的構(gòu)造函數(shù),與默認(rèn)構(gòu)造函數(shù)類似,但是使用自定義分配器來(lái)分配共享狀態(tài)。
3、拷貝構(gòu)造函數(shù)和普通=賦值運(yùn)算符默認(rèn)禁止
4、移動(dòng)構(gòu)造函數(shù)
5、移動(dòng)賦值運(yùn)算符
- std::future僅在創(chuàng)建它的std::promise(或者std::async、std::packaged_task)有效時(shí)才有用,所以可以在使用前用valid()判斷
- std::future可供異步操作創(chuàng)建者用各種方式查詢、等待、提取需要共享的值,也可以阻塞當(dāng)前線程等待到異步線程提供值。
- std::future一個(gè)實(shí)例只能與一個(gè)異步線程相關(guān)聯(lián),多個(gè)線程則需要使用std::shared_future。
std::promise
成員函數(shù):
名稱 | 作用 |
---|---|
operator= | 從另一個(gè) std::promise 移動(dòng)到當(dāng)前對(duì)象。 |
swap() | 交換移動(dòng)兩個(gè) std::promise。 |
get_future() | 獲取與其管理的std::future |
set_value() | 設(shè)置共享狀態(tài)值,此后promise共享狀態(tài)標(biāo)識(shí)變?yōu)閞eady |
set_value_at_thread_exit() | 設(shè)置共享狀態(tài)的值,但是不將共享狀態(tài)的標(biāo)志設(shè)置為 ready,當(dāng)線程退出時(shí)該 promise 對(duì)象會(huì)自動(dòng)設(shè)置為 ready |
set_exception() | 設(shè)置異常,此后promise的共享狀態(tài)標(biāo)識(shí)變?yōu)閞eady |
set_exception_at_thread_exit() | 設(shè)置異常,但是到該線程結(jié)束時(shí)才會(huì)發(fā)出通知 |
1、std::promise::get_future:返回一個(gè)與promise共享狀態(tài)相關(guān)聯(lián)的future對(duì)象
2、std::promise::set_value:設(shè)置共享狀態(tài)的值,此后promise共享狀態(tài)標(biāo)識(shí)變?yōu)閞eady
3、std::promise::set_exception:為promise設(shè)置異常,此后promise的共享狀態(tài)標(biāo)識(shí)變?yōu)閞eady
4、std::promise::set_value_at_thread_exit:設(shè)置共享狀態(tài)的值,但是不將共享狀態(tài)的標(biāo)志設(shè)置為 ready,當(dāng)線程退出時(shí)該 promise 對(duì)象會(huì)自動(dòng)設(shè)置為 ready(注意:該線程已設(shè)置promise的值,如果在線程結(jié)束之后有其他修改共享狀態(tài)值的操作,會(huì)拋出future_error(promise_already_satisfied)異常)
5、std::promise::swap:交換 promise 的共享狀態(tài)
- std::promise的set相關(guān)函數(shù)和get_future()只能被調(diào)用一次,多次調(diào)用會(huì)拋出異常
- std::promise作為使用者的異步線程中應(yīng)當(dāng)注意到共享變量的生命周期、是否被set問(wèn)題。如果沒(méi)被set而線程就結(jié)束了,future端就會(huì)拋出異常。
多線程std::shared_future
std::future 有個(gè)非常明顯的問(wèn)題,就是只能和一個(gè) std::promise 成對(duì)綁定使用,也就意味著僅限于兩個(gè)線程之間使用。
那么多個(gè)線程是否可以呢,可以!就是 std::shared_future。
std::shared_future 也是一個(gè)模板類,它的功能定位、函數(shù)接口和 std::future 一致,不同的是它允許給多個(gè)線程去使用,讓多個(gè)線程去同步、共享:
它的語(yǔ)法是:
【語(yǔ)法】【偽代碼】std::shared_future<Type> s_fu(pt.get_future());
#include <iostream> #include <future> #include <thread> using namespace std; using namespace std::chrono; void futureHandleEntry(std::shared_future<int>& future) { if (future.valid()) { future.wait(); std::cout << "thread:[" << std::this_thread::get_id() << "] value=" << future.get() << std::endl; std::cout << "thread:[" << std::this_thread::get_id() << "] quit!!!" << std::endl; } else { std::cout << "future is invalid!!!" << std::endl; } } int main() { std::promise<int> promise; //獲取shared_future用于多線程訪問(wèn)異步操作結(jié)果 std::shared_future<int> future = promise.get_future(); std::thread t1(&futureHandleEntry, ref(future)); std::thread t2(&futureHandleEntry, ref(future)); std::thread t3(&futureHandleEntry, ref(future)); std::cout << "main thread running!!!" << std::endl; //主線程sleep5s后去set_value std::this_thread::sleep_for(std::chrono::seconds(5)); promise.set_value(10); t1.join(); t2.join(); t3.join(); }
promise和future進(jìn)階
我們知道異常的場(chǎng)景:
1、當(dāng)重復(fù)調(diào)用promise的set_value會(huì)導(dǎo)致拋出異常
#include <iostream> #include <thread> #include <future> #include <chrono> using namespace std; void threadEntry(std::future<int>& future) { try { auto value = future.get(); std::cout << "value=" << value << std::endl; } catch (std::future_error& error) { std::cerr << error.code() << "\n" << error.what() << std::endl; } } int main() { std::promise<int> promise; std::future<int> future = promise.get_future(); std::thread t1(threadEntry, ref(future)); /*主線程promise多次調(diào)用set_value會(huì)拋出future_error異常 */ std::this_thread::sleep_for(std::chrono::seconds(2)); promise.set_value(1); promise.set_value(1); t1.join(); }
在linux中運(yùn)行結(jié)果如下: 會(huì)有Promise already satisfied的錯(cuò)誤提示
2、 當(dāng)std::promise不設(shè)置值時(shí)線程就退出
如果promise直到銷毀時(shí),都未設(shè)置過(guò)任何值,則promise會(huì)在析構(gòu)時(shí)自動(dòng)設(shè)置為std::future_error,這會(huì)造成std::future.get拋出std::future_error異常。
#include <iostream> // std::cout, std::endl #include <thread> // std::thread #include <future> // std::promise, std::future #include <chrono> // seconds using namespace std::chrono; void read(std::future<int> future) { try { future.get(); } catch(std::future_error &e) { std::cerr << e.code() << "\n" << e.what() << std::endl; } } int main() { std::thread thread; { // 如果promise不設(shè)置任何值 // 則在promise析構(gòu)時(shí)會(huì)自動(dòng)設(shè)置為future_error // 這會(huì)造成future.get拋出該異常 std::promise<int> promise; thread = std::thread(read, promise.get_future()); } thread.join(); return 0; }
3、通過(guò)std::promise讓std::future拋出異常
通過(guò)std::promise.set_exception函數(shù)可以設(shè)置自定義異常,該異常最終會(huì)被傳遞到std::future,并在其get函數(shù)中被拋出。
#include <iostream> #include <future> #include <thread> #include <exception> // std::make_exception_ptr #include <stdexcept> // std::logic_error void catch_error(std::future<void> &future) { try { future.get(); } catch (std::logic_error &e) { std::cerr << "logic_error: " << e.what() << std::endl; } } int main() { std::promise<void> promise; std::future<void> future = promise.get_future(); std::thread thread(catch_error, std::ref(future)); // 自定義異常需要使用make_exception_ptr轉(zhuǎn)換一下 promise.set_exception( std::make_exception_ptr(std::logic_error("caught"))); thread.join(); return 0; }
std::promise雖然支持自定義異常,但它并不直接接受異常對(duì)象:
// std::promise::set_exception函數(shù)原型 2void set_exception(std::exception_ptr p);
自定義異??梢酝ㄟ^(guò)位于頭文件exception下的std::make_exception_ptr函數(shù)轉(zhuǎn)化為std::exception_ptr
std::promise
通過(guò)上面的例子,我們看到std::promise<void>
是合法的。此時(shí)std::promise.set_value不接受任何參數(shù),僅用于通知關(guān)聯(lián)的std::future.get()解除阻塞。
std::promise所在線程退出時(shí)
std::async(異步運(yùn)行)時(shí),開(kāi)發(fā)人員有時(shí)會(huì)對(duì)std::promise所在線程退出時(shí)間比較關(guān)注。std::promise支持定制線程退出時(shí)的行為:
- std::promise.set_value_at_thread_exit 線程退出時(shí),std::future收到通過(guò)該函數(shù)設(shè)置的值
- std::promise.set_exception_at_thread_exit 線程退出時(shí),std::future則拋出該函數(shù)指定的異常。
到此這篇關(guān)于深入了解C++11中promise和future的使用的文章就介紹到這了,更多相關(guān)C++ promise future內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
C++深度優(yōu)先搜索的實(shí)現(xiàn)方法
這篇文章主要介紹了C++深度優(yōu)先搜索的實(shí)現(xiàn)方法,是數(shù)據(jù)結(jié)構(gòu)中非常重要的一種算法,需要的朋友可以參考下2014-08-08C++可執(zhí)行文件絕對(duì)路徑值與VS安全檢查詳解
這篇文章主要給大家介紹了關(guān)于C++可執(zhí)行文件絕對(duì)路徑值與VS安全檢查的相關(guān)資料,文中通過(guò)圖文以及實(shí)例代碼介紹的非常詳細(xì),對(duì)大家學(xué)習(xí)或者使用C++具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友可以參考下2023-01-01opencv檢測(cè)直線方法之形態(tài)學(xué)方法
這篇文章主要為大家詳細(xì)介紹了opencv檢測(cè)直線方法之形態(tài)學(xué)方法,具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2018-12-12C++?opencv實(shí)現(xiàn)在圖片上畫一條線示例代碼
這篇文章主要為大家介紹了C++?opencv實(shí)現(xiàn)在圖片上畫一條線的示例代碼,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪2022-05-05詳解state狀態(tài)模式及在C++設(shè)計(jì)模式編程中的使用實(shí)例
這篇文章主要介紹了state狀態(tài)模式及在C++設(shè)計(jì)模式編程中的使用實(shí)例,在設(shè)計(jì)模式中策略用來(lái)處理算法變化,而狀態(tài)則是透明地處理狀態(tài)變化,需要的朋友可以參考下2016-03-03C語(yǔ)言:利用指針編寫程序,用梯形法計(jì)算給定的定積分實(shí)例
今天小編就為大家分享一篇C語(yǔ)言:利用指針編寫程序,用梯形法計(jì)算給定的定積分實(shí)例,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。一起跟隨小編過(guò)來(lái)看看吧2019-12-12