std::async 默认可能延迟执行,需显式指定 std::launch::async 才强制立即异步;future.get() 仅能调用一次,重复调用抛异常;捕获局部变量须注意生命周期,避免引用已销毁栈对象;thread_local 不跨 async 调用持久化。

std::async 启动后不执行?检查 launch 策略
默认情况下 std::async 不一定立刻启动新线程——它可能延迟到第一次调用 get() 或 wait() 才执行,这是由 std::launch::deferred 策略导致的。你写完 std::async(...) 就去干别的事,结果发现任务根本没跑。
- 显式指定
std::launch::async强制立即异步:auto fut = std::async(std::launch::async, []{ /* work */ }); - 不指定策略时,编译器可自由选择
async或deferred;MSVC 常选后者,GCC/Clang 默认行为也未必一致 - 若需“发出去就不管”,又不确定策略,务必加
std::launch::async,否则容易误以为代码卡死或逻辑失效
future.get() 调用一次就失效,重复调用会抛异常
std::future 是一次性消费对象,get() 返回值并使 future 进入无效状态,再次调用会触发 std::future_error(错误码为 std::future_errc::no_state)。
- 常见错误:在日志里反复打印
fut.get(),或在循环中多次检查结果 - 正确做法:用
valid()判断是否还能取值:if (fut.valid()) { auto res = fut.get(); } - 如果只是想等完成不取值,用
fut.wait(),它不改变 future 状态,也不抛异常 - 注意:
std::shared_future可多次get(),但需从std::future显式 move 构造而来
捕获 lambda 中的局部变量,别忘了生命周期管理
异步任务常通过 lambda 捕获局部变量,但若主线程提前退出、栈帧销毁,而异步任务还在访问这些变量,就会触发未定义行为——不是崩溃就是读到垃圾值。
- 避免 [&] 捕获局部指针/引用:比如
int x = 42; auto f = std::async([&]{ return x; });—— 危险! - 安全做法是 [=] 值捕获,或显式拷贝关键数据:
auto f = std::async([x]{ return x * 2; }); - 若必须传对象,优先传
std::shared_ptr,而非裸指针或引用;尤其注意容器迭代器、临时字符串视图(std::string_view)这类轻量但依赖原数据生存期的类型 - 调试技巧:在 lambda 入口加日志,配合
std::this_thread::sleep_for拖慢执行,更容易暴露生命周期问题
async + 线程局部存储(TLS)失效?因为线程不复用
std::async 默认每次调用都可能创建新线程(取决于 launch 策略),而线程局部变量(thread_local)在线程结束时自动析构。这意味着你不能指望某个 thread_local 变量在多次 std::async 调用间保持状态。
立即学习“C++免费学习笔记(深入)”;
- 典型误用:用
thread_local std::mt19937 rng做随机数生成器,结果每次 async 都拿到相同种子(初始状态) - 解决方案:要么把状态封装进 lambda 捕获(如
[rng = std::mt19937{std::random_device{}()}]),要么改用全局/静态对象(注意线程安全) - 不要依赖线程池语义:标准库不保证
std::async复用线程,即便你连续调用十次,也可能对应十个独立线程
事情说清了就结束。真正难的不是语法,是判断哪些数据该拷贝、哪些该共享、哪些根本不能跨线程碰——尤其是当 async 和类成员函数、智能指针、自定义分配器混在一起的时候。











