先看一个容易被忽略的细节:std::async 如果不显式指定启动策略,你根本没法保证它真的异步。默认行为下,它可能一上来就开新线程执行,也可能拖到你调用 get() 时才同步跑——而且具体选哪种,完全取决于编译器和运行时环境。这可不是什么“平台差异”的小问题,踩过坑的人都知道,线上环境突然变串行、排查半天找不到原因,多半就是这里埋的雷。
显式指定 std::launch::async 才能确保任务真正在新线程中立即执行;默认调用 std::async 不保证异步,可能延迟到 get() 时才同步运行。

std::launch::async 强制创建线程的硬性要求
标准白纸黑字写得很清楚:只要传入 std::launch::async,实现必须启动新线程执行任务,否则抛出 std::system_error(错误码为 std::errc::resource_una vailable_try_again)。这和“尽量异步”的默认行为完全不同——一个是必须,一个是可能。
- 每次调用都触发线程创建,栈空间(通常 1MB)、内核对象、调度上下文全量分配
- 不等待
future.get()或future.wait(),任务在std::async返回前就已进入就绪/运行态 - 若系统线程数已达上限(如 Linux 的
/proc/sys/kernel/threads-max),直接失败,不会退化为deferred - 适合必须并行、不可延迟的关键路径,比如实时数据预处理、多路传感器采集
std::launch::deferred 是纯同步延迟,零线程开销
std::launch::deferred 不是“轻量异步”,它根本不是异步——函数体完全不执行,直到你调用 future.get() 或 future.wait(),且执行发生在当前线程(即调用 get() 的那个线程)。
- 无任何线程创建、切换、销毁成本,适合短计算或条件性触发逻辑
- 不能用于解耦耗时操作,因为调用
get()时才会真正跑函数,主线程照样卡住 - 多个
deferredfuture 连续get()是串行执行,毫无并发性 - 注意副作用:若函数有静态局部变量初始化,首次
get()才触发初始化,行为与普通函数调用一致
默认策略(async | deferred)为什么危险
不显式指定策略时,std::async 接收的是 std::launch::async | std::launch::deferred,这意味着运行时可任意选择一种——而标准未规定选择逻辑,各实现差异极大。
- libstdc++(GCC)倾向优先用
async,但高负载下可能 fallback 到deferred - libc++(Clang)某些版本在单核或资源紧张时直接选
deferred - 结果不可预测:同一段代码,在开发机上并发跑得好,上线后突然变串行,排查极难
- 隐式依赖运行时决策,违反“显式优于隐式”原则,也破坏单元测试稳定性
get() 调用时机对 async 和 deferred 的实际影响
表面看两者都要等 get() 拿结果,但阻塞性质完全不同:
- 对
async:get()只是同步等待已完成/进行中的异步任务,主线程可先做其他事 - 对
deferred:get()是**触发点+执行点+阻塞点**三合一,函数此时才开始跑,且全程独占当前线程 - 典型误用:把
deferred当成“懒加载异步”,结果发现 UI 线程在get()时卡死 200ms - 安全做法:只对确定轻量、可预测、且允许同步执行的逻辑用
deferred,例如配置项解析、简单字符串拼接
最易被忽略的一点:std::future 析构时若未调用 get() 或 wait(),对 async 策略会阻塞析构(等待任务结束),而 deferred 则直接丢弃任务——这种静默丢弃可能掩盖逻辑错误。务必确保 future 生命周期可控。