Wiki
future、async、promise 與 packaged_task
本文由簡體中文內容確定性轉換,並受版本化術語表保護。
std::thread 很適合表達:
启动一个线程
↓
在线程里执行某个函数
但它本身並不直接解決:
线程函数怎么把返回值交回来?
线程里抛出的异常怎么传回来?
主线程怎么知道结果准备好了?
多个线程之间怎么建立“结果通道”?
C++11 在 <future> 中提供了一組更偏向“任務與結果”的併發工具:
std::future
std::async
std::promise
std::packaged_task
std::shared_future
它們的核心思想可以概括成一句話:
結果現在還沒有,但未來會產生;先拿到一個 future,之後再等待或取得結果。
所以:
std::thread
→ 更关心“线程本身”
std::future / std::async / std::promise
→ 更关心“任务最终产生什么结果”
1.future 和 async 基礎
1.1.std::future<T> 是什麼
std::future<T> 可以理解成:
一個“未來結果”的讀取端。
例如:
std::future<int>
表示:
未来某个时刻
会有一个 int 结果
如果任務沒有返回值,則可以使用:
std::future<void>
1.2.future 背後其實有一個 shared state
future 本身並不是結果,也不是線程。
它關聯的是一個:
共享狀態(shared state)
可以理解成:
生产者
写入结果 / 异常
↓
shared state
↓
future
读取结果 / 异常
共享狀態裏可能保存:
任务返回值
任务抛出的异常
结果是否已经准备好
等待和同步所需的信息
所以:
future.get();
真正做的事情可以理解成:
如果结果还没准备好
→ 等待
结果准备好以后
→ 取出结果
如果任务保存的是异常
→ 在这里重新抛出
1.3.最簡單的 std::async
來看一個最簡單的例子:
#include <future>
#include <print>
int calculate(int a, int b)
{
return a + b;
}
int main()
{
std::future<int> result = std::async(
std::launch::async,
calculate,
10,
20);
std::println("main continues");
std::println("{}", result.get());
}
可能輸出:
main continues
30
這裏:
std::async(
std::launch::async,
calculate,
10,
20);
可以理解成:
提交 calculate(10, 20) 这个任务
↓
让它异步执行
↓
返回一个 future<int>
這個:
std::future<int> result
就是未來結果的讀取端。
主線程繼續執行:
std::println("main continues");
直到:
result.get();
如果任務已經完成:
直接取得结果
如果任務還沒完成:
当前线程在这里等待
所以:
std::async負責“提交任務”,std::future負責“以後拿結果”。
1.4.async 的參數和 thread 很像
基本形式:
std::async(policy, callable, arg1, arg2, ...);
例如:
auto result = std::async(
std::launch::async,
calculate,
10,
20);
其中:
policy
→ 任务怎么启动
callable
→ 要执行的函数 / lambda / 函数对象
arg1, arg2...
→ 传给这个可调用对象的参数
可調用對象可以是:
普通函数
lambda
函数对象
成员函数
如果需要傳引用,同樣需要考慮:
std::ref(x)
以及引用對象的生命週期問題。
2.async 啓動策略
2.1.std::launch::async
std::async 最常用的啓動策略之一是:
std::launch::async
例如:
auto result = std::async(
std::launch::async,
task);
它表示:
要求這個任務異步執行。
可以先直觀理解成:
现在把任务安排出去执行
当前线程继续干自己的事
以后通过 future 等结果
重點是:
调用方关心的是任务和结果
而不是手动管理 thread 对象
所以它和:
std::thread
的使用視角不一樣。
std::thread 更像:
我要创建并管理一个线程
std::async 更像:
我要提交一个任务
最后拿它的结果
2.2.std::launch::deferred
另一個啓動策略是:
std::launch::deferred
例如:
auto result = std::async(
std::launch::deferred,
task);
它並不會立刻把任務放到後台執行。
它表示:
先把這個任務記下來,等真正有人等待結果時再執行。
例如:
#include <future>
#include <print>
#include <thread>
int task()
{
std::println(
"task thread: {}",
std::this_thread::get_id());
return 42;
}
int main()
{
std::println(
"main thread: {}",
std::this_thread::get_id());
auto result = std::async(
std::launch::deferred,
task);
std::println("before get");
std::println("{}", result.get());
}
可以理解成:
创建 async
↓
task 此时还没执行
↓
main 继续运行
↓
执行 result.get()
↓
就在调用 get() 的线程中执行 task()
↓
得到结果
所以 deferred 並不是:
后台线程晚一点执行
而更接近:
延迟到真正需要结果时
再由等待结果的线程执行
2.3.async 和 deferred 的區別
可以簡單記成:
std::launch::async
→ 任务异步执行
→ 当前线程可以继续
→ future 以后取结果
而:
std::launch::deferred
→ 任务先不执行
→ 第一次 get()/wait() 时才执行
→ 由调用等待函数的线程执行
所以:
async
≈ “现在就安排执行”
deferred
≈ “先记下来,用到结果时再执行”
2.4.不寫 launch policy 會怎樣
可以這樣寫:
auto result = std::async(task);
但這種情況下,標準允許實現自己選擇:
async
或者
deferred
所以不要把:
std::async(task);
自動理解成:
一定立刻创建后台线程并发执行
如果你明確要求:
我就是想讓任務真正異步執行。
那最好顯式寫:
std::launch::async
例如:
auto result = std::async(
std::launch::async,
task);
3.future 等待與異常
3.1.future::get()
get() 是 future 最重要的函數之一:
auto value = future.get();
它做兩件事:
1. 等待结果准备好
2. 取得结果
例如:
auto result = std::async(
std::launch::async,
[] {
return 42;
});
int value = result.get();
如果任務還沒執行完:
get()
→ 等
任務結束以後:
get()
→ 返回 42
3.2.普通 future::get() 通常只能調用一次
例如:
auto result = std::async(
std::launch::async,
[] {
return 42;
});
int value = result.get();
這裏:
result.get();
把這個 future 裏的結果取走了。
之後:
result.valid()
通常會變成:
false
所以普通 std::future 可以理解成:
一次性的取件憑證。
结果准备好
↓
get()
↓
结果被取走
↓
future 不再关联有效共享状态
因此不要這樣寫:
result.get();
result.get();
第二次 get() 會對無效 future 操作,通常會拋出:
std::future_error
3.3.future::valid()
可以通過:
future.valid()
查詢:
這個 future 當前是否還關聯着有效的共享狀態。
例如:
auto result = std::async(
std::launch::async,
[] {
return 42;
});
std::println("{}", result.valid());
result.get();
std::println("{}", result.valid());
可以理解成:
get() 前
→ future 还关联共享状态
get() 后
→ 普通 future 的结果已经被消费
所以:
valid()
不是:
“任务成功了吗?”
而是:
“这个 future 现在还有没有关联一个有效共享状态?”
3.4.future::wait()
如果你只想:
等任務結束,但暫時不取結果。
可以:
future.wait();
例如:
result.wait();
std::println("task finished");
int value = result.get();
這裏:
wait()
只負責等待。
它不會把結果取走。
所以之後仍然可以:
result.get();
可以記成:
wait()
→ 等待,但不消费结果
get()
→ 等待 + 取走结果
3.5.wait() 不會把任務異常直接拋出來
假設異步任務拋出了異常。
調用:
future.wait();
只是等待共享狀態變為 ready。
它不會在這裏重新拋出任務異常。
異常通常是在:
future.get();
時重新拋出。
所以:
wait()
→ 确认任务已经结束
get()
→ 真正取得“结果或者异常”
3.6.future::wait_for()
可以等待一段時間:
auto status = result.wait_for(
std::chrono::milliseconds(100));
返回值可能是:
std::future_status::ready
std::future_status::timeout
std::future_status::deferred
分別表示:
ready
→ 结果已经准备好了
timeout
→ 等待时间到了,但结果还没准备好
deferred
→ 这个任务使用 deferred 策略
示例:
#include <chrono>
#include <future>
#include <print>
#include <thread>
using namespace std::chrono_literals;
int main()
{
auto result = std::async(
std::launch::async,
[] {
std::this_thread::sleep_for(1s);
return 42;
});
if (result.wait_for(100ms)
== std::future_status::timeout)
{
std::println("not ready yet");
}
std::println("{}", result.get());
}
輸出:
not ready yet
42
整個過程是:
任务开始
↓
main 最多等 100ms
↓
任务还没完成
↓
wait_for() 返回 timeout
↓
main 继续
↓
最后 get() 等待并取得结果
3.7.future_status::deferred
如果:
wait_for(...)
返回:
std::future_status::deferred
説明:
這個任務根本不是正在後台慢慢執行。
它只是一個延遲任務。
例如:
auto result = std::async(
std::launch::deferred,
task);
此時不斷寫:
result.wait_for(100ms);
result.wait_for(100ms);
result.wait_for(100ms);
並不會讓它“等着等着就在後台完成”。
因為 deferred 的邏輯是:
真正执行 get()/wait()
↓
调用这些函数的线程亲自执行任务
3.8.異步任務裏的異常會通過 future 傳回來
這是 future 很重要的能力。
例如:
#include <future>
#include <print>
#include <stdexcept>
int main()
{
auto result = std::async(
std::launch::async,
[]() -> int {
throw std::runtime_error("task failed");
});
try
{
std::println("{}", result.get());
}
catch (const std::exception& e)
{
std::println("{}", e.what());
}
}
輸出:
task failed
這裏任務線程拋出的:
std::runtime_error
不會直接從工作線程“跳到”主線程。
實際邏輯是:
异步任务抛异常
↓
异常被保存到 shared state
↓
main 调用 future.get()
↓
get() 发现共享状态里保存的是异常
↓
在 main 这里重新抛出
所以:
future.get();
取得的不一定是:
正常返回值
也可能是:
任务失败时保存的异常
這使得異步任務的錯誤處理很像普通函數:
普通函数
→ 调用处 catch
异步任务
→ future.get() 处 catch
4.std::promise<T>
前面的:
std::async
會自動創建結果通道。
但有時候我們想自己控制:
什么时候写入结果
在哪个线程写入结果
写入正常值还是异常
這時可以使用:
std::promise<T>
它可以理解成:
future 對應結果通道的“寫入端”。
關係是:
promise<T>
│
│ set_value()
│ set_exception()
↓
shared state
↓
future<T>
│
│ get()
↓
读取结果
所以最簡單的記憶方式是:
promise
→ 写
future
→ 读
4.1.promise::get_future()
創建:
std::promise<int> promise;
以後,可以通過:
std::future<int> future = promise.get_future();
獲得和這個 promise 關聯的讀取端。
於是:
promise
和
future
通過同一個 shared state 連接起來。
4.2.promise 最簡單的例子
#include <future>
#include <print>
#include <thread>
void producer(std::promise<int> promise)
{
promise.set_value(42);
}
int main()
{
std::promise<int> promise;
std::future<int> future =
promise.get_future();
std::thread thread(
producer,
std::move(promise));
std::println("{}", future.get());
thread.join();
}
輸出:
42
整個過程可以理解成:
main 创建 promise
↓
main 从 promise 拿到 future
↓
main 把 promise 移动给 worker
↓
worker 调用 set_value(42)
↓
42 被写入 shared state
↓
main 的 future.get()
↓
读出 42
4.3.為什麼 promise 要 std::move
std::promise 不能隨意複製。
它是:
move-only
所以:
std::thread thread(
producer,
std::move(promise));
是在把 promise 的所有權移動給工作線程。
可以理解成:
main
→ 保留 future,负责读
worker
→ 拿走 promise,负责写
這樣:
worker 写结果
↓
shared state
↓
main 读结果
就建立起來了。
4.4.promise::set_value()
生產端可以:
promise.set_value(42);
它表示:
把最終結果寫入共享狀態。
寫入以後:
future
→ ready
等待中的:
future.get();
就可以繼續執行。
對於:
std::promise<void>
沒有具體返回值。
可以:
promise.set_value();
表示:
任務已經成功完成。
4.5.promise 的結果通常只能設置一次
例如:
promise.set_value(42);
promise.set_value(100);
是不對的。
同一個共享狀態只能完成一次。
第一次:
set_value(42);
已經把結果確定了。
第二次再次:
set_value(...)
通常會拋:
std::future_error
所以可以理解成:
一个 promise
→ 最终只能提交一次最终结果
4.6.promise::set_exception()
promise 不僅能寫正常值:
promise.set_value(result);
也可以寫異常:
promise.set_exception(
std::current_exception());
常見寫法:
void producer(std::promise<int> promise)
{
try
{
int result = do_work();
promise.set_value(result);
}
catch (...)
{
promise.set_exception(
std::current_exception());
}
}
這樣 consumer:
future.get();
會:
任务成功
→ 返回结果
任务失败
→ 重新抛出保存的异常
4.7.完整的 promise 異常示例
#include <exception>
#include <future>
#include <print>
#include <stdexcept>
#include <thread>
void producer(std::promise<int> promise)
{
try
{
throw std::runtime_error("failed");
}
catch (...)
{
promise.set_exception(
std::current_exception());
}
}
int main()
{
std::promise<int> promise;
auto future = promise.get_future();
std::thread thread(
producer,
std::move(promise));
try
{
std::println("{}", future.get());
}
catch (const std::exception& e)
{
std::println("{}", e.what());
}
thread.join();
}
輸出:
failed
流程是:
worker 发生异常
↓
catch 捕获异常
↓
promise.set_exception(...)
↓
异常进入 shared state
↓
main 调用 future.get()
↓
异常在 main 中重新抛出
4.8.什麼是 broken promise
假設:
std::future<int> future;
{
std::promise<int> promise;
future = promise.get_future();
}
這裏 promise 離開作用域時:
没有 set_value()
也没有 set_exception()
但 future 還在等待結果。
這種情況叫:
broken promise
也就是:
原本负责生产结果的人已经消失
但结果还没有产生
標準庫不會讓 future 永遠傻等。
之後:
future.get();
會得到一個對應的:
std::future_error
可以把它理解成:
“結果生產端已經斷了,不會再有正常結果了。”
4.9.promise 的所有結束路徑都應該有明確結果
如果使用:
std::promise
生產端最好認真處理:
成功
→ set_value()
失败
→ set_exception()
提前退出 / 取消
→ 也要设计清楚最终结果
否則就容易出現:
broken promise
5.std::packaged_task
std::packaged_task 解決的是另一類問題。
假設已經有:
int add(int a, int b)
{
return a + b;
}
現在想把它變成:
一个“执行以后自动把结果写到 future”的任务
可以使用:
std::packaged_task
它可以理解成:
把一個可調用對象包裝成一個“自帶 future 結果通道”的任務對象。
5.1.packaged_task 最簡單的例子
#include <future>
#include <print>
int add(int a, int b)
{
return a + b;
}
int main()
{
std::packaged_task<int(int, int)> task(add);
std::future<int> result =
task.get_future();
task(10, 20);
std::println("{}", result.get());
}
輸出:
30
這裏:
std::packaged_task<int(int, int)>
表示:
这个任务:
接收两个 int
返回一个 int
創建:
std::packaged_task<int(int, int)> task(add);
以後:
task(10, 20);
實際上會:
调用 add(10, 20)
↓
得到返回值 30
↓
自动把 30 写入 shared state
↓
result.get() 取得 30
5.2.packaged_task 和 promise 最大的區別
promise 是:
你自己決定什麼時候寫結果。
例如:
promise.set_value(42);
而 packaged_task 是:
你給它一個函數,執行這個函數時,它自動把函數的返回值寫入結果通道。
所以:
promise
→ 手动写结果
packaged_task
→ 执行函数,自动写结果
5.3.packaged_task 會自動傳遞異常
假設被包裝的函數:
int work()
{
throw std::runtime_error("failed");
}
包裝成:
std::packaged_task<int()> task(work);
執行:
task();
如果 work() 拋異常,packaged_task 會把異常保存到共享狀態。
之後:
future.get();
會重新拋出這個異常。
所以它不只會自動處理:
返回值
也會自動處理:
异常
5.4.packaged_task 和 thread 配合
可以把 packaged task 交給線程執行:
#include <future>
#include <print>
#include <thread>
int calculate()
{
return 42;
}
int main()
{
std::packaged_task<int()> task(calculate);
std::future<int> result =
task.get_future();
std::thread thread(
std::move(task));
std::println("{}", result.get());
thread.join();
}
輸出:
42
這裏:
std::thread thread(
std::move(task));
表示:
把 task 移动给新线程
↓
新线程执行 task()
↓
task 内部调用 calculate()
↓
返回值写入 shared state
↓
result.get() 得到 42
5.5.packaged_task 也是 move-only
和 promise 類似:
std::packaged_task
也不能隨意複製。
通常需要:
std::move(task)
把它移動到:
线程
任务队列
worker
中。
5.6.為什麼 packaged_task 很適合任務隊列
線程池通常會有這種思想:
用户提交函数
↓
包装成任务对象
↓
放进任务队列
↓
worker 线程取任务
↓
执行任务
↓
结果写进 shared state
↓
用户拿 future 等结果
這正好和:
std::packaged_task
的設計非常契合。
因為 packaged task 本身已經把:
怎么执行任务
+
结果写到哪里
綁定在一起了。
5.7.promise 和 packaged_task 怎麼選
可以這樣記:
5.7.1.promise
適合:
我自己决定结果什么时候产生
结果不一定直接来自某个函数返回值
生产过程可能分很多步骤
例如:
promise.set_value(result);
由你自己控制。
5.7.2.packaged_task
適合:
我已经有一个函数 / lambda
我就想把它包装成一个“执行后有 future 结果”的任务
例如:
std::packaged_task<int()> task(calculate);
然後:
task();
自動把返回值送進 future。
所以最簡單的區別是:
promise
→ 手动生产结果
packaged_task
→ 包装可调用对象,自动生产结果
6.std::shared_future
普通:
std::future<T>
更適合:
一个消费者
因為:
future.get();
通常只能消費一次。
如果:
多個線程都需要讀取同一個異步結果
可以使用:
std::shared_future<T>
6.1.future 轉成 shared_future
例如:
auto future = std::async(
std::launch::async,
[] {
return 42;
});
std::shared_future<int> shared =
future.share();
之後可以:
shared.get();
shared.get();
shared.get();
多個地方都可以讀取同一個結果。
6.2.shared_future 更像“共享查看”
普通 future:
get()
→ 把结果取走
→ future 失效
而 shared future:
get()
→ 查看共享结果
→ 不会因为这次 get() 就把结果消费掉
所以:
future
→ 单消费者
shared_future
→ 多消费者
例如:
一次性加载配置
↓
得到 shared_future<Config>
↓
多个线程等待同一份配置
↓
全部读取同一个结果
7.關係與選擇
7.1.這幾個東西到底是什麼關係
可以把整個 <future> 體系理解成:
shared state
│
┌─────────────┴─────────────┐
│ │
写入端 读取端
│ │
│ future
│ shared_future
│
├─ promise
│ → 手动写结果
│
├─ packaged_task
│ → 执行函数后自动写结果
│
└─ async
→ 提交任务并自动管理结果通道
其中:
future
→ 负责以后读取结果
promise
→ 负责手动写结果
packaged_task
→ 把函数包装成“执行后自动写结果”的任务
async
→ 最方便的高级接口,直接提交任务并得到 future
shared_future
→ 允许多个消费者读取同一个结果
7.2.async 和 thread 怎麼選
如果你關注的是:
我需要一个长期存在的线程
线程里面一直循环
我要控制它什么时候停止
我要管理生命周期
更適合:
std::thread
std::jthread
例如:
串口接收线程
传感器采集线程
机器人后台 worker
长期循环任务
如果你關注的是:
我有一个任务
它会算出一个结果
我以后要取得这个结果
任务中的异常也要传回来
更適合:
std::async
std::future
例如:
并行计算一段结果
后台加载文件
异步计算某个值
7.3.async 不是線程池
不要把:
std::async
理解成:
C++ 标准线程池
它並不是標準線程池接口。
如果你需要:
固定数量 worker
不断提交大量小任务
任务队列
长期后台执行
可控停止
通常應該考慮:
线程池
std::jthread
专门的任务系统
更完整的异步框架
而不是不斷:
std::async(...)
8.常見錯誤
8.1.錯誤:認為 std::async() 一定創建新線程
例如:
std::async(task);
如果不指定 policy,標準允許選擇:
async
或
deferred
如果明確要求真正異步:
std::async(
std::launch::async,
task);
8.2.錯誤:對普通 future 重複 get()
錯誤:
future.get();
future.get();
普通 future 的結果通常只能消費一次。
8.3.錯誤:忘記 get() 還會重新拋出異常
例如:
auto result = std::async(
std::launch::async,
task);
result.get();
這裏不僅可能拿到返回值,也可能拋出任務裏的異常。
所以必要時要:
try
{
result.get();
}
catch (...)
{
}
8.4.錯誤:promise 沒寫結果就被銷燬
如果 promise:
既没有 set_value()
也没有 set_exception()
就消失了,future 會得到:
broken promise
8.5.錯誤:把 promise / packaged_task 當可複製對象
它們通常是:
move-only
跨線程轉移時經常需要:
std::move(...)
8.6.錯誤:用 async 做長期後台循環
例如:
std::async(
std::launch::async,
[] {
while (true)
{
...
}
});
如果這個任務需要:
明确停止
长期运行
生命周期管理
通常:
std::jthread
更符合語義。
9.最後總結
這組工具最核心的區別可以記成:
std::future
→ “以后我要读结果”
std::promise
→ “我手动把结果写进去”
std::packaged_task
→ “我执行这个函数,结果自动写进去”
std::async
→ “帮我提交任务,并直接给我 future”
std::shared_future
→ “多个地方都要读同一个结果”
其中:
std::thread / std::jthread
→ 更偏线程生命周期
std::future / std::async
→ 更偏任务与结果
如果只記一張圖:
生产结果
↓
promise / packaged_task / async
↓
shared state
↓
future / shared_future
↓
读取结果
這就是整個 <future> 體系最核心的邏輯。