Effective Modern C++ 条款 36:明确指定 std::launch::async 策略
引言:异步编程的艺术
在现代 C++ 并发编程中,std::async 能够协调多个线程执行任务。然而,其默认行为暗藏玄机——并不总是如期望那样立即启动异步任务。本文将探讨 std::async 的启动策略,揭示默认行为的潜在陷阱,并展示如何确保真正的异步执行。
一、std::async 的两种启动策略
std::async 提供了两种基本的启动策略:
std::launch::async- 函数必须异步执行,即在不同的线程上立即开始。std::launch::deferred- 函数仅在调用get()或wait()时才开始执行,且在当前线程上同步执行。
| 启动策略 | 执行时机 |
|---|---|
std::launch::async | 立即在新线程执行 |
std::launch::deferred | 延迟到 get/wait 调用时执行 |
二、默认策略的'双重人格'
令人惊讶的是,std::async 的默认策略并非上述任何一种,而是二者的'或'组合:
auto fut1 = std::async(f); // 默认策略
auto fut2 = std::async(std::launch::async | std::launch::deferred, f); // 等效写法
这种设计赋予了标准库极大的灵活性,使其能够智能管理线程资源、避免线程创建开销并实现负载均衡。然而,这种灵活性也带来了三个关键的不确定性:
- 并发性不确定:函数
f可能与调用线程并发执行,也可能不会。 - 线程归属不确定:
f可能在任何线程上执行。 - 执行性不确定:
f甚至可能永远不会执行。
三、默认策略的潜在陷阱
1. thread_local 变量的不确定性
当函数使用线程局部存储 (thread_local) 时,我们无法预测哪个线程的变量会被访问:
thread_local int tlsVar = 0;
void f() {
tlsVar = 42;
}
fut = std::(f);


