std::async默认采用std::launch::deferred策略,调用get()才执行,不启新线程;需显式指定std::launch::async才能真正异步;返回的future必须被消费,否则析构时可能阻塞;异常只能通过get()捕获;lambda捕获需注意对象生命周期。

std::async 默认是延迟执行,不是立刻启动
很多人以为 std::async 一调用就开新线程跑任务,结果发现主线程卡住、或者任务根本没执行——这是因为默认策略是 std::launch::deferred,即“等你调 get() 才执行”,本质是同步延迟调用,不启线程。
要真正异步,必须显式指定启动策略:
-
std::async(std::launch::async, ...):强制新开线程(最常用) -
std::async(std::launch::async | std::launch::deferred, ...):让运行时决定(但实践中几乎总选async)
漏写 std::launch::async 是新手最高频的坑,尤其在调试时看到“没并发”就懵了。
返回的 future 必须被消费,否则可能死锁或资源泄漏
std::async 返回 std::future,而 C++11 标准规定:如果这个 std::future 对象在析构前从未调用过 get() 或 wait(),且它是通过 std::launch::async 启动的,那么析构会**阻塞等待任务完成**——这在局部作用域里容易引发意料之外的阻塞。
典型错误写法:
void bad_example() {
std::async(std::launch::async, []{ do_something(); }); // 没接 future,析构时卡住!
}
正确做法:
- 明确接收并处理
std::future,哪怕只调wait() - 若真不需要结果,用
std::future<void></void>+wait() - 避免让它在作用域末尾静默析构
捕获异常必须通过 future::get(),不能靠 try/catch 包 async 调用
异步任务里抛的异常不会传播到主线程,而是被“捕获并存储”在 std::future 内部。直接在外层 try/catch 包 std::async 调用毫无意义。
必须在调用 get() 时才重新抛出:
auto f = std::async(std::launch::async, []{
throw std::runtime_error("boom");
});
// 这里不报错
try {
f.get(); // 这里才真正 throw
} catch (const std::exception& e) {
// 处理异常
}
漏掉 get() 的异常处理,会导致程序在后续某次不经意的 get() 时突然崩溃,堆栈还指向很远的地方。
注意 lambda 捕获和对象生命周期,别传悬挂引用
异步任务可能比创建它的作用域活得更久,所以传参要格外小心:
- 值捕获(
[=]或[x](int y){...})相对安全,但注意大对象拷贝开销 - 引用捕获(
[&]或[&x])极危险:若x在任务执行前已销毁,就是未定义行为 - 传指针/引用参数时,确保所指对象生命周期覆盖整个异步执行过程
常见翻车场景:在循环里用 [&i] 捕获循环变量,结果所有任务都读到同一个(已失效的)i。
std::async 看似简单,但策略选择、future 生命周期、异常传递、捕获语义这四点,任一疏忽都会导致隐蔽的并发 bug,而且往往在压力测试或上线后才暴露。
C++免费学习笔记(深入):立即使用
在学习笔记中,你将探索 C++ 的入门与实战技巧!











