C++17 多线程系列(四):异步编程——future / promise / async / packaged_task
·
核心目标:掌握 C++ 异步编程四大组件,能用
std::async和std::future进行异步任务编排,理解 shared state 的通信模型,避免常见陷阱。前置知识:Part 1 的线程创建,Part 2 的 mutex 基础,lambda 表达式。
4.1 异步编程模型概述
4.1.1 同步 vs 异步
4.1.2 C++ 异步体系全景
4.2 std::future——获取异步结果
4.2.1 基本用法
#include <future>
#include <iostream>
std::future<int> the_answer;
void set_answer() {
// 稍后通过 promise 设置值(4.3 详讲)
}
int main() {
auto fut = the_answer;
// fut.get() 会阻塞,直到值被设置
// std::cout << fut.get() << "\n";
}
4.2.2 核心 API
std::future<int> fut = /* from async/promise/packaged_task */;
// get(): 阻塞等待 + 获取值(只能调用一次!)
int result = fut.get();
// fut.valid() == false 此后
// wait(): 仅等待,不获取值
fut.wait();
// wait_for(): 带超时等待
auto status = fut.wait_for(std::chrono::seconds(1));
if (status == std::future_status::ready) {
// 就绪
} else if (status == std::future_status::timeout) {
// 超时
} else if (status == std::future_status::deferred) {
// 延迟执行 (async 的 deferred 策略)
}
// wait_until(): 等待到指定时间点
auto deadline = std::chrono::steady_clock::now() + std::chrono::seconds(5);
fut.wait_until(deadline);
// valid(): 检查是否与 shared state 关联
if (fut.valid()) { /* ... */ }
4.2.3 future 是 move-only
std::future<int> f1 = std::async([]{ return 42; });
// ❌ 不能拷贝
// std::future<int> f2 = f1; // 编译错误!
// ✅ 可以移动
std::future<int> f2 = std::move(f1);
// f1.valid() == false
4.3 std::promise——手动传值的通道
4.3.1 基本使用
#include <future>
#include <thread>
void compute_in_thread(std::promise<int> p, int x, int y) {
// 执行耗时计算
std::this_thread::sleep_for(std::chrono::seconds(1));
int result = x * x + y * y;
p.set_value(result); // 设置结果, 唤醒 future
}
int main() {
std::promise<int> p;
std::future<int> fut = p.get_future();
std::thread t(compute_in_thread, std::move(p), 3, 4);
std::cout << "Waiting for result...\n";
int result = fut.get(); // 阻塞, 直到 set_value
std::cout << "Result: " << result << "\n"; // 25
t.join();
}
4.3.2 传输异常
void risky_computation(std::promise<int> p) {
try {
throw std::runtime_error("computation failed");
p.set_value(42); // 不会执行
} catch (...) {
p.set_exception(std::current_exception()); // 传递异常
}
}
int main() {
std::promise<int> p;
auto fut = p.get_future();
std::thread t(risky_computation, std::move(p));
try {
int result = fut.get(); // 这里重新抛出异常!
} catch (const std::exception& e) {
std::cout << "Caught: " << e.what() << "\n";
}
t.join();
}
4.3.3 promise-future 通信模型
4.4 std::async——一行启动异步任务
4.4.1 基本用法
#include <future>
#include <iostream>
int heavy_computation(int n) {
int sum = 0;
for (int i = 0; i < n * 1000000; ++i) {
sum += i;
}
return sum;
}
int main() {
// 一行代码: 启动异步计算
std::future<int> fut = std::async(heavy_computation, 100);
// 主线程可以做其他事情
std::cout << "Computing in background...\n";
// 获取结果 (阻塞等待)
int result = fut.get();
std::cout << "Result: " << result << "\n";
}
4.4.2 三种启动策略
// ═══ 策略 1: std::launch::async — 强制异步 (新线程) ═══
auto f1 = std::async(std::launch::async, []{ return 42; });
// ✅ 保证在新线程中执行
// ═══ 策略 2: std::launch::deferred — 延迟执行 ═══
auto f2 = std::async(std::launch::deferred, []{ return 42; });
// 任务推迟到 f2.get() / f2.wait() 时才在当前线程执行
// → 没有新线程创建, 相当于 lazy evaluation
// ═══ 策略 3: 默认 (async | deferred) — 危险! ═══
auto f3 = std::async([]{ return 42; });
// 运行时自行决定 async 还是 deferred
// ⚠️ 行为不确定!
| 策略 | 新线程? | 何时执行 | 何时用 |
|---|---|---|---|
std::launch::async |
✅ 是 | 立即 (新线程) | 明确要并行 |
std::launch::deferred |
❌ 否 | get()/wait() 时 |
lazy 计算 |
async | deferred (默认) |
❓ 不确定 | ❓ 不确定 | 不建议 |
最佳实践:始终显式指定策略,不要依赖默认。
4.4.3 async 返回的 future 析构会阻塞
这是 std::async 最臭名昭著的陷阱:
void dangerous() {
// ❌ 临时 future 对象立即析构 → 阻塞!
std::async(std::launch::async, [] {
std::this_thread::sleep_for(std::chrono::seconds(5));
return 42;
});
// ↑ future 析构函数会阻塞等待任务完成!
// dangerous() 会卡住 5 秒
}
void safe() {
// ✅ 把 future 存到变量里, 延长生命周期
auto fut = std::async(std::launch::async, [] {
std::this_thread::sleep_for(std::chrono::seconds(5));
return 42;
});
// 现在可以继续执行, 稍后 fut.get()
}
4.4.4 async vs 手动 thread
| 特性 | std::async |
手动 std::thread |
|---|---|---|
| 代码量 | 1 行 | 5+ 行 |
| 返回值 | ✅ future 自动 |
❌ 需手动传参/共享变量 |
| 异常传播 | ✅ future.get() 自动 |
❌ 需手动 catch + 传递 |
| 线程管理 | 自动 join/detach | 必须手动处理 |
| 线程数控制 | ❌ 不可控 | ✅ 可控 (配合线程池) |
4.5 std::packaged_task——包装可调用对象
4.5.1 基本用法
#include <future>
#include <thread>
int factorial(int n) {
int result = 1;
for (int i = 2; i <= n; ++i) result *= i;
return result;
}
int main() {
// packaged_task 包装一个可调用对象
std::packaged_task<int(int)> task(factorial);
// task 本身是可调用的 (operator())
// 获取关联的 future
std::future<int> fut = task.get_future();
// 将 task 传递给工作线程
std::thread t(std::move(task), 10); // 执行 factorial(10)
t.join();
std::cout << "10! = " << fut.get() << "\n"; // 3628800
}
4.5.2 典型场景:GUI 线程分发任务
// GUI 线程需要把耗时任务丢给工作线程
class TaskDispatcher {
std::deque<std::packaged_task<void()>> tasks_;
std::mutex mtx_;
std::condition_variable cv_;
std::thread worker_;
public:
TaskDispatcher() {
worker_ = std::thread([this] {
while (true) {
std::packaged_task<void()> task;
{
std::unique_lock lock(mtx_);
cv_.wait(lock, [this]{ return !tasks_.empty(); });
task = std::move(tasks_.front());
tasks_.pop_front();
}
task(); // 执行
}
});
}
template <typename F>
std::future<void> submit(F&& f) {
std::packaged_task<void()> task(std::forward<F>(f));
auto fut = task.get_future();
{
std::lock_guard lock(mtx_);
tasks_.push_back(std::move(task));
}
cv_.notify_one();
return fut;
}
};
4.6 std::shared_future——多消费者
4.6.1 为什么需要 shared_future
std::promise<int> p;
std::future<int> fut = p.get_future();
// ❌ future 只能 get() 一次
// int v1 = fut.get(); // 获取
// int v2 = fut.get(); // ❌ valid() == false
// ✅ shared_future 可以被多次获取
std::shared_future<int> shared = fut.share();
// 或直接: std::shared_future<int> shared = p.get_future();
int v1 = shared.get(); // ✅
int v2 = shared.get(); // ✅ 仍然有效
4.6.2 多个线程等待同一个结果
std::promise<int> p;
std::shared_future<int> shared = p.get_future();
auto worker = [shared](int id) {
int result = shared.get(); // 全部阻塞, 等待同一个值
std::cout << "Thread " << id << " got: " << result << "\n";
};
std::thread t1(worker, 1);
std::thread t2(worker, 2);
std::thread t3(worker, 3);
std::this_thread::sleep_for(std::chrono::seconds(1));
p.set_value(42); // 所有 wait 同时被唤醒!
t1.join(); t2.join(); t3.join();
// 输出 (顺序不定):
// Thread 1 got: 42
// Thread 3 got: 42
// Thread 2 got: 42
4.7 异常处理
void throwing_task(std::promise<int> p) {
p.set_exception(
std::make_exception_ptr(std::runtime_error("task failed")));
}
int main() {
// async 自动传播异常
auto fut = std::async(std::launch::async, []() -> int {
throw std::runtime_error("async failed");
return 0;
});
try {
int x = fut.get(); // 异常在此重新抛出
} catch (const std::exception& e) {
std::cout << "Caught from async: " << e.what() << "\n";
}
// packaged_task 同样自动传播
std::packaged_task<int()> task([]() -> int {
throw std::runtime_error("task failed");
return 0;
});
auto fut2 = task.get_future();
task();
try {
fut2.get();
} catch (const std::exception& e) {
std::cout << "Caught from task: " << e.what() << "\n";
}
}
4.8 常见陷阱与最佳实践
陷阱 1:future 析构阻塞
// ❌ 临时 future 立即析构 → 该行卡住 5 秒
std::async(std::launch::async, heavy_work);
// ✅ 存为变量
auto fut = std::async(std::launch::async, heavy_work);
// 稍后 fut.get();
陷阱 2:默认启动策略
// ❌ 不确定: 开新线程还是 lazy?
auto f1 = std::async(heavy_work);
// ✅ 显式指定
auto f2 = std::async(std::launch::async, heavy_work);
陷阱 3:线程数爆炸
// ❌ 循环创建 async → 可能创建成千上万个线程
for (int i = 0; i < 100000; ++i) {
auto fut = std::async(std::launch::async, [i] { return i * i; });
}
// ✅ 限制并发度 (用线程池)
ThreadPool pool(8); // Part 7 详解
for (int i = 0; i < 100000; ++i) {
pool.submit([i] { return i * i; });
}
陷阱 4:promise 生命周期
// ❌ promise 析构了但 future 还活着 → future.get() 抛 broken_promise
std::future<int> bad() {
std::promise<int> p;
auto fut = p.get_future();
return fut; // p 被销毁, fut 关联的 shared state 被 abandon
}
// ✅ 确保 promise 在 future 之前不销毁
void good() {
std::promise<int> p;
auto fut = p.get_future();
std::thread([p = std::move(p)]() mutable {
p.set_value(42); // promise 移动到线程中, 生命周期被延长
}).detach();
}
4.9 组件速查表
| 组件 | 角色 | 创建方式 | 获取结果 |
|---|---|---|---|
std::async |
Provider | std::async(policy, func, args) |
返回 future |
std::promise<T> |
Provider | p.get_future() 绑定 |
p.set_value(v) |
std::packaged_task<R(Args...)> |
Provider | task(args...) 执行 |
task.get_future() |
std::future<T> |
Consumer | 从 Provider 获得 | fut.get() (一次) |
std::shared_future<T> |
Consumer | fut.share() |
shared.get() (多次) |
4.10 小结
| 知识点 | 掌握程度 | 核心要点 |
|---|---|---|
| future 基本 API | 掌握 | get/wait/wait_for/valid, move-only |
| promise-future 通信 | 掌握 | set_value → shared state → get |
| async 启动策略 | 掌握 | 显式指定 async,不用默认 |
| future 析构阻塞 | 掌握 | async 的 future 析构会阻塞,必须存变量 |
| packaged_task | 理解 | 包装 callable,适合任务队列 |
| shared_future | 掌握 | 多消费者共享,fut.share() 获得 |
| 异常传播 | 掌握 | set_exception → get 时重新抛出 |
| 线程数爆炸 | 了解 | async 循环 → 配合线程池 |
下期预告
[Part 5:C++17 并行算法] 将展示如何用一行改动将 STL 算法并行化:
std::execution::par/par_unseq执行策略std::sort/std::for_each/std::reduce的并行版本- 性能对比:串行 vs 并行 vs 并行+向量化
- 并行算法的异常处理与注意事项
推荐工具
- Compiler Explorer (godbolt.org) —— 查看 async 生成的线程创建代码
top -H—— 监控 async 创建的线程数-fsanitize=thread—— 检测 promise/future 的错误使用
更多推荐


所有评论(0)