std::async 默认使用 std::launch::deferred | std::launch::async 组合策略,具体由实现决定,常见编译器倾向延迟执行(deferred),即调用 get() 或 wait() 时才在当前线程同步运行,不创建新线程。

std::async 默认用什么策略启动任务?
默认不是总开新线程——它用的是 std::launch::deferred | std::launch::async 组合策略,具体选哪个由实现决定,但常见编译器(如 GCC、MSVC)在未显式指定时倾向延迟执行(deferred),也就是调用 get() 或 wait() 时才同步运行,根本不动线程。
这意味着你写:
auto fut = std::async([]{ return 42; });
很可能啥线程都没创建,直到你调用 fut.get() 才在当前线程执行。这不是 bug,是标准允许的行为。
- 想强制新开线程?必须显式传
std::launch::async - 想确保延迟执行?传
std::launch::deferred - 混用两个 flag(如
std::launch::async | std::launch::deferred)没意义,标准禁止
怎么确保任务一定在新线程里跑?
只有一种可靠方式:显式指定 std::launch::async 策略,并且别依赖默认行为。
立即学习“C++免费学习笔记(深入)”;
auto fut = std::async(std::launch::async, []{
std::this_thread::sleep_for(std::chrono::seconds(1));
return 42;
});
这时系统必须立即派生新线程(或从线程池取一个)来执行 lambda。验证方法很简单:在 lambda 里打日志 + 查看线程 ID,或者用调试器观察线程数变化。
- 别省略第一个参数——漏掉就退回默认策略,行为不可控
- 如果任务很快,新线程开销可能比执行本身还大,注意权衡
- Windows 上 MSVC 的
std::async实现曾有线程复用逻辑,但只要用了async标志,语义上仍是“异步启动”,不保证复用哪个线程
std::launch::deferred 有什么实际用途?
它本质是“懒执行”包装器,适合做条件性、按需触发的计算,比如缓存未命中时才真正算,且不希望提前占资源。
典型场景:配置解析、重试逻辑封装、避免构造开销。
- 返回
std::future后,不调get()/wait()就不会执行函数体 -
deferred任务永远在调用get()的线程上执行(即同步、无并发),所以不用考虑线程安全 - 不能和
async混用;也不能用于需要并行执行多个任务的场景 - 错误现象:以为开了后台线程,结果
get()一调整个主线程卡住——其实是deferred在同步执行
为什么有时候 std::async 不开线程,甚至卡死?
最常见原因有两个:一是没指定 std::launch::async,走了默认策略(可能是 deferred);二是 std::future 对象被销毁前没调 get() 或 wait(),而它内部持有一个阻塞析构锁(C++11 规定:若 future 关联 deferred 任务,析构时会强制执行;若关联 async 任务,析构会阻塞等待完成)。
- 后果:局部
std::future变量离开作用域时卡住当前线程,尤其在循环或短生命周期函数里容易中招 - 解法:明确策略 + 显式管理生命周期,例如用
std::shared_future避免重复析构,或用std::future::valid()判断再决定是否get() - GCC libstdc++ 曾在某些版本对 deferred future 析构做同步执行,导致意外阻塞,升级标准库可缓解
线程调度不是黑盒,std::async 的策略开关就是你唯一能握在手里的控制柄。漏掉 std::launch::async,等于把线程创建权交给了编译器实现——而它通常选择最省事的方式,不是你想要的方式。









