網站首頁 編程語言 正文
Promise和Future
原理
C++11中promise和future機制是用于并發編程的一種解決方案,用于在不同線程完成數據傳遞(異步操作)。
傳統方式通過回調函數處理異步返回的結果,導致代碼邏輯分散且難以維護。
Promise和Future是一種提供訪問異步操作結果的機制,可以在線程之間傳遞數據和異常消息。
應用場景:顧客在一家奶茶店點了單,服務員給顧客一個單號,當奶茶做好后,服務員更新排號的狀態,顧客可以去做自己的事情了,顧客可以通過查詢排號來得知奶茶是否做好,當查到奶茶做好了就可以回來取奶茶了。
#include <iostream>
#include <future>
#include <thread>
using namespace std;
using namespace std::chrono;
void WaitForMilkTea(future<int>& future)
{
/*其中獲取future結果有三種方式
1、auto value = future.get() get()方法會阻塞等待異步操作結束并返回結果
2、std::future_status 方式判斷狀態 有deferred、timeout、ready三種狀態
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; //異步操作還沒開始
} else if (status == std::future_status::timeout) {
std::cout << "timeout!!!" << std::endl; //異步操作超時
} else if (status == std::future_status::ready) {
std::cout << "ready!!!" << std::endl; //異步操作已經完成
}
} while (status != std::future_status::ready);
//通過判斷future_status狀態為ready時才通過get()獲取值
auto notice = future.get();
std::cout << "WaitForMilkTea receive value:" << notice << std::endl;
#endif
//get()方法
#if 0
auto notice = future.get(); //get阻塞等待直到異步處理結束返回值
std::cout << "WaitForMilkTea receive value:" << notice << std::endl;
#endif
//wait()方法
future.wait(); //和get()區別是wait只等待異步操作完成,沒有返回值
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相關聯的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 | 等待超時 |
deferred | 2 | 延遲執行(與std::async配合使用) |
future_errc 枚舉 : 為 future_error 類報告的所有錯誤提供符號名稱。
名稱 | 值 | 示意 |
---|---|---|
broken_promise | 0 | 與其關聯的 std::promise 生命周期提前結束 |
future_already_retrieved | 1 | 重復調用 get() 函數 |
promise_already_satisfied | 2 | 與其關聯的 std::promise 重復 set |
no_state | 4 | 無共享狀態 |
Promise和Future模型
流程如下:
1.線程1初始化一個promise和future對象,將promise對象傳遞給線程2,相當于線程2對線程1的一個承諾
2.future相當于一個承諾,用于獲取未來線程2的值
3.線程2接受一個promise,需要將處理結果通過promise返回給線程1
4.線程1想要獲取數據,此時線程2還未返回promise就阻塞等待處,直到線程2的數據可達
promise相關函數
std::future負責訪問, std::future是一個模板類,它提供了可供訪問異步執行結果的一種方式。
std::promise負責存儲, std::promise也是一個模板類,它提供了存儲異步執行結果的值和異常的一種方式。
總結:std::future負責訪問,std::promise負責存儲,同時promise是future的管理者
std::future
名稱 | 作用 |
---|---|
operator= | 移動 future 對象,移動! |
share() | 返回一個可在多個線程中共享的 std::shared_future 對象 |
get() | 獲取值(類型由模板類型決定) |
valid() | 檢查 future 是否處于被使用狀態,也就是它被首次在首次調用 get() 或 share() 前。建議使用前加上valid()判斷 |
wait() | 阻塞等待調用它的線程到共享值成功返回 |
wait_for() | 在規定時間內 阻塞等待調用它的線程到共享值成功返回 |
wait_until() | 在指定時間節點內 阻塞等待調用它的線程到共享值成功返回 |
1、普通構造函數, 默認無參構造函數
2、帶自定義內存分配器的構造函數,與默認構造函數類似,但是使用自定義分配器來分配共享狀態。
3、拷貝構造函數和普通=賦值運算符默認禁止
4、移動構造函數
5、移動賦值運算符
- std::future僅在創建它的std::promise(或者std::async、std::packaged_task)有效時才有用,所以可以在使用前用valid()判斷
- std::future可供異步操作創建者用各種方式查詢、等待、提取需要共享的值,也可以阻塞當前線程等待到異步線程提供值。
- std::future一個實例只能與一個異步線程相關聯,多個線程則需要使用std::shared_future。
std::promise
成員函數:
名稱 | 作用 |
---|---|
operator= | 從另一個 std::promise 移動到當前對象。 |
swap() | 交換移動兩個 std::promise。 |
get_future() | 獲取與其管理的std::future |
set_value() | 設置共享狀態值,此后promise共享狀態標識變為ready |
set_value_at_thread_exit() | 設置共享狀態的值,但是不將共享狀態的標志設置為 ready,當線程退出時該 promise 對象會自動設置為 ready |
set_exception() | 設置異常,此后promise的共享狀態標識變為ready |
set_exception_at_thread_exit() | 設置異常,但是到該線程結束時才會發出通知 |
1、std::promise::get_future:返回一個與promise共享狀態相關聯的future對象
2、std::promise::set_value:設置共享狀態的值,此后promise共享狀態標識變為ready
3、std::promise::set_exception:為promise設置異常,此后promise的共享狀態標識變為ready
4、std::promise::set_value_at_thread_exit:設置共享狀態的值,但是不將共享狀態的標志設置為 ready,當線程退出時該 promise 對象會自動設置為 ready(注意:該線程已設置promise的值,如果在線程結束之后有其他修改共享狀態值的操作,會拋出future_error(promise_already_satisfied)異常)
5、std::promise::swap:交換 promise 的共享狀態
- std::promise的set相關函數和get_future()只能被調用一次,多次調用會拋出異常
- std::promise作為使用者的異步線程中應當注意到共享變量的生命周期、是否被set問題。如果沒被set而線程就結束了,future端就會拋出異常。
多線程std::shared_future
std::future 有個非常明顯的問題,就是只能和一個 std::promise 成對綁定使用,也就意味著僅限于兩個線程之間使用。
那么多個線程是否可以呢,可以!就是 std::shared_future。
std::shared_future 也是一個模板類,它的功能定位、函數接口和 std::future 一致,不同的是它允許給多個線程去使用,讓多個線程去同步、共享:
它的語法是:
【語法】【偽代碼】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用于多線程訪問異步操作結果
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進階
我們知道異常的場景:
1、當重復調用promise的set_value會導致拋出異常
#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多次調用set_value會拋出future_error異常
*/
std::this_thread::sleep_for(std::chrono::seconds(2));
promise.set_value(1);
promise.set_value(1);
t1.join();
}
在linux中運行結果如下: 會有Promise already satisfied的錯誤提示
2、 當std::promise不設置值時線程就退出
如果promise直到銷毀時,都未設置過任何值,則promise會在析構時自動設置為std::future_error,這會造成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不設置任何值
// 則在promise析構時會自動設置為future_error
// 這會造成future.get拋出該異常
std::promise<int> promise;
thread = std::thread(read, promise.get_future());
}
thread.join();
return 0;
}
3、通過std::promise讓std::future拋出異常
通過std::promise.set_exception函數可以設置自定義異常,該異常最終會被傳遞到std::future,并在其get函數中被拋出。
#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轉換一下
promise.set_exception(
std::make_exception_ptr(std::logic_error("caught")));
thread.join();
return 0;
}
std::promise雖然支持自定義異常,但它并不直接接受異常對象:
// std::promise::set_exception函數原型
2void set_exception(std::exception_ptr p);
自定義異常可以通過位于頭文件exception下的std::make_exception_ptr函數轉化為std::exception_ptr
std::promise
通過上面的例子,我們看到std::promise<void>
是合法的。此時std::promise.set_value不接受任何參數,僅用于通知關聯的std::future.get()解除阻塞。
std::promise所在線程退出時
std::async(異步運行)時,開發人員有時會對std::promise所在線程退出時間比較關注。std::promise支持定制線程退出時的行為:
- std::promise.set_value_at_thread_exit 線程退出時,std::future收到通過該函數設置的值
- std::promise.set_exception_at_thread_exit 線程退出時,std::future則拋出該函數指定的異常。
原文鏈接:https://blog.csdn.net/weixin_45312249/article/details/127772999
相關推薦
- 2022-09-11 Oracle學習筆記之視圖及索引的使用_oracle
- 2022-03-20 基于ABP框架實現數據字典開發_實用技巧
- 2024-01-08 AOP獲取方法返回值
- 2022-12-08 用C語言求解一元二次方程的簡單實現_C 語言
- 2022-05-03 使用EF的Code?First模式操作數據庫_實用技巧
- 2022-02-04 SQL語句:空值判斷
- 2022-10-31 一篇文章教會你使用gs_restore導入數據_數據庫其它
- 2022-04-06 python中matplotlib的顏色以及形狀實例詳解_python
- 最近更新
-
- window11 系統安裝 yarn
- 超詳細win安裝深度學習環境2025年最新版(
- Linux 中運行的top命令 怎么退出?
- MySQL 中decimal 的用法? 存儲小
- get 、set 、toString 方法的使
- @Resource和 @Autowired注解
- Java基礎操作-- 運算符,流程控制 Flo
- 1. Int 和Integer 的區別,Jav
- spring @retryable不生效的一種
- Spring Security之認證信息的處理
- Spring Security之認證過濾器
- Spring Security概述快速入門
- Spring Security之配置體系
- 【SpringBoot】SpringCache
- Spring Security之基于方法配置權
- redisson分布式鎖中waittime的設
- maven:解決release錯誤:Artif
- restTemplate使用總結
- Spring Security之安全異常處理
- MybatisPlus優雅實現加密?
- Spring ioc容器與Bean的生命周期。
- 【探索SpringCloud】服務發現-Nac
- Spring Security之基于HttpR
- Redis 底層數據結構-簡單動態字符串(SD
- arthas操作spring被代理目標對象命令
- Spring中的單例模式應用詳解
- 聊聊消息隊列,發送消息的4種方式
- bootspring第三方資源配置管理
- GIT同步修改后的遠程分支