【Effective Modern C++】第七章 lambda表达式:36. 如有异步的必要请指定async

📅 发布时间:2026/7/5 20:01:35 👁️ 浏览次数:
【Effective Modern C++】第七章 lambda表达式:36. 如有异步的必要请指定async
std::async 默认启动策略的本质与问题std::async的默认启动策略并非单一的异步执行std::launch::async而是std::launch::async | std::launch::deferred异步 延迟执行的组合。其底层逻辑是系统自主决定任务是异步在新线程执行还是延迟到调用future的get()/wait()时同步执行。这一设计的初衷是让标准库灵活管理线程资源避免资源超额 / 线程耗尽但核心问题是默认策略完全失去了对任务执行方式的可控性引发一系列不确定性。默认策略引发的执行不确定性坑 1执行方式不可控无法预测任务是否与调用std::async的线程并发执行无法预测任务是否在调用get()/wait()的线程而非新线程上执行。坑 2线程本地存储TLS访问混乱若任务读写thread_local变量无法确定访问的是 “新线程的 TLS” 还是 “调用get()/wait()线程的 TLS”。坑 3任务可能永不执行若程序所有路径都未调用future的get()/wait()延迟执行的任务会直接跳过完全不运行。坑 4超时等待循环死循环对延迟任务调用wait_for()/wait_until()会始终返回std::future_status::deferred导致判断 “任务是否就绪ready” 的循环永远无法终止仅在系统资源耗尽、任务被延迟时暴露。解决不确定性显式指定 std::launch::async 的逻辑原理 1显式策略锁定异步执行std::launch::async策略强制任务必须在新线程异步执行直接消除 “延迟执行” 带来的所有不确定性是 “异步执行” 需求的核心解决方案。原理 2先检查延迟状态避免死循环若无法完全放弃默认策略的灵活性仍需兼容延迟场景需先通过wait_for(0s)判断任务是否为延迟状态再处理超时循环规避死循环风险。解决方案方案 1确保任务异步执行直接指定std::launch::async作为第一个参数强制异步auto fut std::async(std::launch::async, f); // 强制f在新线程异步执行方案 2封装工具函数简化异步调用封装reallyAsync函数默认强制异步避免重复指定策略// C14版本自动推导返回值 templatetypename F, typename... Ts inline auto reallyAsync(F f, Ts... params) { return std::async(std::launch::async, std::forwardF(f), std::forwardTs(params)...); } // 使用等价于显式指定std::launch::async auto fut reallyAsync(f);方案 3修复超时等待循环兼容延迟场景先判断延迟状态再处理循环auto fut std::async(f); if (fut.wait_for(0s) std::future_status::deferred) { // 延迟任务同步调用调用get()/wait()执行f fut.get(); } else { // 异步任务带超时的循环等待不会死循环 while (fut.wait_for(100ms) ! std::future_status::ready) { // 任务未就绪时的并行处理逻辑 } }总结std::async默认策略是async|deferred系统自主选择异步 / 延迟执行导致执行方式、TLS 访问、任务执行与否均不可控异步执行是核心需求时必须显式指定std::launch::async也可封装reallyAsync函数简化使用若需兼容延迟场景需先通过wait_for(0s)检查延迟状态避免超时循环死循环。原著在线阅读地址