std::async默认策略可能导致惰性执行而非立即异步,需显式指定std::launch::async;std::future析构会隐式阻塞等待;异常仅在get()时抛出;资源泄漏风险高,须谨慎管理生命周期。

std::async 默认启动策略容易被忽略
std::async 不一定真正异步执行——它默认使用 std::launch::deferred | std::launch::async 策略,意味着调用时可能不立即启动线程,而是在首次调用 get() 或 wait() 时才惰性执行。这和多数人直觉里的“一调就跑”不符,尤其在调试或性能敏感场景下容易误判。
- 显式指定
std::launch::async才能确保立刻派生新线程(前提是系统资源允许) - 若未指定策略且函数体耗时极短,可能被调度到主线程延迟执行,导致
get()阻塞时间远超预期 - Windows 下某些旧版 MSVC(如 VS2015)对
std::launch::deferred实现有缺陷,可能意外触发同步行为
std::future 的生命周期必须管理清楚
std::future 对象析构时会阻塞等待结果,这是隐式同步点。如果把 std::async 返回的 std::future 存在局部变量里但没显式调用 get() 或 wait(),函数退出时就会卡住——不是崩溃,而是静默阻塞。
- 避免裸存
std::future:优先用auto声明,并确保作用域结束前处理完 - 若需延迟取值,用
std::shared_future替代,允许多次get()且可跨作用域传递 - 切勿返回局部
std::future:移动语义虽支持,但一旦绑定的异步状态销毁(如临时对象析构),get()会抛出std::future_error
异常传播机制与 try-catch 的位置很关键
异步函数内抛出的异常不会立即暴露,而是被封装进 std::future,直到调用 get() 时才重新抛出。这意味着你在 std::async 调用处加 try 没用,必须在 get() 处捕获。
- 错误写法:
try { auto f = std::async(...); }—— 异常此时还没发生 - 正确写法:
auto f = std::async(...); try { f.get(); } catch (const std::exception& e) { ... } - 如果忘了
get(),异常永远不抛出;如果多次调用get(),第二次会抛std::future_error(状态已取走)
线程资源泄漏风险比想象中高
std::async 使用线程池还是每次新建线程,标准未规定,由实现决定。libstdc++(GCC)和 libc++(Clang)通常复用线程,但 MSVC 的早期版本曾为每个 std::async 创建独立线程且不回收,高频调用会导致句柄耗尽、std::system_error 报错 Resource temporarily unavailable。
立即学习“C++免费学习笔记(深入)”;
- 避免在循环中无节制调用
std::async,尤其配合std::launch::async - 考虑改用
std::thread+std::packaged_task手动管理,或引入第三方线程池(如 tbb::task_group) - 检查
std::async返回的std::future是否已被取值:未取值的 future 会一直持有线程资源,直到析构
真正麻烦的不是语法怎么写,而是 async 的“懒”和 future 的“等”交织在一起,稍不注意,程序就停在某个看不见的 get() 或析构点上,连栈回溯都看不出问题在哪。



















