必须显式指定std::launch::async才能保证真异步,否则可能退化为同步调用;std::future析构时会阻塞直至任务完成,未调用get()或wait()易导致主线程卡住。

不显式指定启动策略,std::async 可能根本没并发 —— 尤其在 MSVC Debug 模式或负载高的系统上,它会退化为同步调用,get() 不阻塞但也不起新线程。
必须显式传 std::launch::async 才能保证真异步
默认策略 std::launch::async | std::launch::deferred 是“由实现决定”,不是“自动选最优”。实际中:MSVC Debug 下几乎总是 deferred;GCC/Clang 在资源紧张时也可能 fallback 到延迟执行。
- 只想要后台线程?写死
std::async(std::launch::async, ...) - 想延迟求值(比如做 lazy init)?明确用
std::launch::deferred - 混用两种策略没意义 ——
async | deferred不等于“自动切换”,只是把决策权交出去,行为不可控 - 如果编译器不支持
std::launch::async(极少见),会抛std::system_error,需捕获
std::future 析构时可能卡住主线程
只要 std::future 对象还活着,且内部状态未就绪(即还没调用过 get() 或 wait()),它的析构函数就会阻塞,直到关联任务结束。这不是 bug,是 C++11 标准强制要求。
- 常见踩坑:在函数末尾声明
auto fut = std::async(...),但没调用get()或wait()就 return —— 函数卡在析构点 - 若只需等待完成、不关心返回值,用
fut.wait()即可,比get()更轻量 - 若任务可能失败且你不想处理异常,至少也得
fut.wait()一次,否则析构仍会等
返回 void 或 move-only 类型时的特殊处理
std::future<void></void> 合法且常用,比如异步日志写入、通知类任务。但它仍需 get() 或 wait() 来同步完成 —— 不是为了取值,而是为了捕获可能发生的异常。
- 返回
std::unique_ptr<int></int>?lambda 必须显式声明返回类型:[]() -> std::unique_ptr<int> { return std::make_unique<int>(42); }</int></int> - 忘记写返回类型,编译器可能推导成
std::unique_ptr<int>&&</int>或报错 “use of deleted function” -
std::future本身不可拷贝,只能移动;多个 future 共享同一状态?那是std::shared_future的事,别误用
异常不是被吞掉,而是被“存起来”等 get() 时重抛
异步任务里 throw 的异常不会崩掉主线程,但也不会静默忽略 —— 它被序列化进 std::future 内部状态,直到你调用 get() 才原样 rethrow。这点比手写线程 + 全局变量安全得多。
- 没调用
get()?异常一直存着,析构future时不抛,但任务其实已失败 - 用
wait_for()判断超时后,仍要调用get()—— 否则异常永远不会暴露 - 若用
std::promise手动设异常,必须用p.set_exception(std::current_exception()),不能直接throw
最易被忽略的是:std::async 创建的线程生命周期完全绑定在 std::future 上,而这个绑定靠析构时的隐式同步维持。一旦忘了碰它,程序表面正常,实则卡在看不见的地方。
C++免费学习笔记(深入):立即使用
在学习笔记中,你将探索 C++ 的入门与实战技巧!











