future、async、promise 與 packaged_task
std::thread 很適合表達“啓動一個線程”,但它本身不直接提供:
- 線程函數返回值;
- 異常自動傳回調用方;
- 一個統一的“未來結果”對象。
C++11 在 <future> 中提供了一組更偏“任務”的併發工具:
std::future
std::async
std::promise
std::packaged_task
std::shared_future
它們的核心思想是:
某個結果現在還沒有,但未來會產生;調用方先拿到一個
future,之後再等待或取得結果。
1. std::future<T> 是什麼
std::future<T> 表示:
未来某个时刻会得到一个 T
例如:
std::future<int>
表示未來會產生一個 int。
如果任務沒有返回值,則使用:
std::future<void>
future 最重要的成員函數包括:
get()
wait()
wait_for()
wait_until()
valid()
2. 最簡單的 std::async
#include <future>
#include <iostream>
int calculate(int a, int b)
{
return a + b;
}
int main()
{
std::future<int> result = std::async(
std::launch::async,
calculate,
10,
20);
std::cout << "main continues\n";
std::cout << result.get() << '\n';
}
這裏:
std::async(...)
啓動一個異步任務,並返回:
std::future<int>
調用:
result.get()
時,如果結果還沒準備好,當前線程會等待;準備好後取得返回值。
輸出可能是:
main continues
30
3. async 的參數和 thread 很像
概念上:
std::async(policy, callable, arg1, arg2, ...)
其中:
policy:啓動策略;callable:要執行的可調用對象;- 後續參數:傳給該可調用對象。
可調用對象同樣可以是:
- 普通函數;
- Lambda;
- 函數對象;
- 成員函數。
4. std::launch::async 和 std::launch::deferred
std::async 支持兩種重要啓動策略。
4.1 std::launch::async
std::async(std::launch::async, task);
要求任務異步執行。
可以把它理解成:
現在就安排任務獨立執行。
4.2 std::launch::deferred
std::async(std::launch::deferred, task);
表示延遲執行。
任務不會立刻運行,而是在第一次:
future.get()
future.wait()
時,由調用這些函數的線程執行。
例如:
#include <future>
#include <iostream>
#include <thread>
int task()
{
std::cout << "task thread: "
<< std::this_thread::get_id()
<< '\n';
return 42;
}
int main()
{
std::cout << "main thread: "
<< std::this_thread::get_id()
<< '\n';
auto result = std::async(std::launch::deferred, task);
std::cout << result.get() << '\n';
}
這裏 task() 會在調用 get() 的線程中執行。
5. 不寫 policy 會怎樣
可以寫:
auto result = std::async(task);
此時標準庫允許實現選擇:
async
或
deferred
因此,如果你明確要求任務真正異步執行,建議顯式寫:
std::launch::async
否則不要假設“不寫策略就一定創建新線程”。
6. future::get() 只能取得一次
普通 std::future 的結果通常只能 get() 一次:
auto result = std::async(std::launch::async, [] {
return 42;
});
int value = result.get();
// result.get(); // 不应再次 get
第一次 get() 後,future 通常不再關聯共享狀態。
可以用:
result.valid()
檢查它是否仍然關聯有效狀態。
7. wait():只等待,不取結果
future.wait();
只等待任務完成,不消費結果。
之後仍然可以:
future.get();
例如:
result.wait();
std::cout << "ready\n";
std::cout << result.get() << '\n';
8. wait_for():等待一段時間
auto status = result.wait_for(std::chrono::milliseconds(100));
返回:
std::future_status::ready
std::future_status::timeout
std::future_status::deferred
示例:
#include <chrono>
#include <future>
#include <iostream>
#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::cout << "not ready yet\n";
}
std::cout << result.get() << '\n';
}
9. 異常會通過 future 傳播
這是 future 相比裸 std::thread 很方便的一點。
如果異步任務拋異常:
#include <future>
#include <iostream>
#include <stdexcept>
int main()
{
auto result = std::async(std::launch::async, []() -> int {
throw std::runtime_error("task failed");
});
try
{
std::cout << result.get() << '\n';
}
catch (const std::exception& e)
{
std::cout << e.what() << '\n';
}
}
異常會被保存到共享狀態中,然後在:
result.get()
時重新拋出。
這比讓工作線程自己想辦法把錯誤傳回主線程方便很多。
10. std::promise<T>
std::promise<T> 可以理解成 future 通道的“寫入端”。
對應關係:
promise<T> ----写入结果----> shared state ----读取结果----> future<T>
通過:
promise.get_future()
取得與它關聯的 future。
示例:
#include <future>
#include <iostream>
#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 t(producer, std::move(promise));
std::cout << future.get() << '\n';
t.join();
}
注意 std::promise 不能隨意複製,因此這裏通過:
std::move(promise)
把 promise 的所有權移動到工作線程。
11. promise::set_value()
promise.set_value(value);
向共享狀態寫入結果。
對於 std::promise<void>:
promise.set_value();
表示任務成功完成,但沒有具體返回值。
12. promise::set_exception()
promise 也可以主動寫入異常:
try
{
...
}
catch (...)
{
promise.set_exception(std::current_exception());
}
之後 future 調用:
future.get()
會重新拋出這個異常。
完整示例:
#include <exception>
#include <future>
#include <iostream>
#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 t(producer, std::move(promise));
try
{
std::cout << future.get() << '\n';
}
catch (const std::exception& e)
{
std::cout << e.what() << '\n';
}
t.join();
}
13. 什麼是 broken promise
如果 promise 在既沒有:
set_value()
也沒有:
set_exception()
的情況下被銷燬,那麼對應 future 不會永遠傻等。
它會得到一個“broken promise”錯誤狀態。
例如:
std::future<int> future;
{
std::promise<int> promise;
future = promise.get_future();
} // promise 销毁,但没有写入结果
future.get(); // 抛出 std::future_error
14. std::packaged_task
std::packaged_task 用來把一個可調用對象包裝成:
執行後自動把返回值或異常寫進共享狀態的任務。
例如:
#include <future>
#include <iostream>
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::cout << result.get() << '\n';
}
這裏:
std::packaged_task<int(int, int)>
表示:
接收两个 int
返回一个 int
15. packaged_task 和 thread 配合
#include <future>
#include <iostream>
#include <thread>
int calculate()
{
return 42;
}
int main()
{
std::packaged_task<int()> task(calculate);
std::future<int> result = task.get_future();
std::thread t(std::move(task));
std::cout << result.get() << '\n';
t.join();
}
packaged_task 本身是 move-only 的,因此傳入線程時需要:
std::move(task)
線程池內部經常會出現類似思想:
把任务包装起来
↓
放入任务队列
↓
worker thread 取出并执行
↓
future 接收结果
16. promise 和 packaged_task 的區別
promise 更像:
我自己決定什麼時候、在什麼邏輯裏把結果寫進去。
而 packaged_task 更像:
我已經有一個可調用對象,執行它時自動把返回值寫進 future。
例如:
promise
适合手动生产结果
packaged_task
适合包装已有函数/任务
17. std::shared_future
普通 std::future 更偏單消費者:
future.get()
通常只能消費一次。
如果多個地方都需要讀取同一個異步結果,可以使用:
std::shared_future<T>
可以由普通 future 轉換:
auto future = std::async(std::launch::async, [] {
return 42;
});
std::shared_future<int> shared = future.share();
之後多個線程可以:
shared.get()
讀取同一個結果。
18. async 和 thread 怎麼選
如果你關心的是:
启动一个长期运行线程
控制线程生命周期
线程循环
明确 join/detach
使用:
std::thread / std::jthread
如果你關心的是:
提交一个任务
未来得到返回值
自动传播异常
可以優先考慮:
std::async / std::future
它們表達的是不同層次的抽象。
19. 常見錯誤
- 不寫 launch policy,卻默認認為
std::async一定創建新線程。 - 對同一個普通
future重複調用get()。 - 忘記處理異步任務在
get()時重新拋出的異常。 promise銷燬前既不set_value()也不set_exception(),產生 broken promise。- 把 move-only 的
promise/packaged_task當成可複製對象使用。 - 為一個長期運行、需要明確停止控制的後台線程強行使用
std::async。
小結
std::future<T>表示未來產生的T結果。std::async適合直接提交一個會返回結果的任務。- 明確要求真正異步執行時,使用
std::launch::async。 future::get()會等待結果並取得值,也會重新拋出異步任務中的異常。std::promise是結果通道的主動寫入端。std::packaged_task把可調用對象包裝成能產生 future 的任務。std::shared_future允許多個消費者讀取同一個結果。thread偏線程生命週期管理,future/async偏任務與結果管理。