-
如上节所述,使用
std::async
相比手动创建线程有若干优点。但是,调用std::async
不保证传入的函数(或 callable object)一定是在其它线程异步运行的。 -
std::async
实际是遵循 运行策略(launch policy) 执行的,标准库在std::launch
枚举类中声明了两种策略(设待执行的函数名为f
):std::launch::async
运行策略表示f
必须 被异步地(即在另一个线程上)执行。std::launch::deferred
运行策略表示只有当std::async
返回的std::future
被调用了get()
或wait()
时,f
才会被执行(这里做了一些简化,详见 Item 38)。换言之,f
的执行被延迟了。当get()
或wait()
被调用时,f
会被 同步地 执行,调用者会被阻塞直到f
完成。如果它们没被调用,则f
永远不会被执行。
-
有趣的是,
std::async
的默认运行策略并非这二者中的任何一个,而是它们取或的结果。因此,默认策略同时允许异步或同步的执行,这使得标准库中的线程管理能有更灵活的控制。
以上代码节选自MSVC的
<future>
文件。launch
的实现方式是常用的 enum class 作为 flag,重点是要为其定义或、异或等位运算符,75行做的就是这件事。_BITMASK_OPS(x)
宏展开就是为类x
定义多个运算符,其中例如|
运算符的实现是取枚举的underlying_type
并做位运算。因此可以使用launch::async | launch::deferred = 01b | 10b = 11b
。
- 由于默认策略可能采取
deferred
策略的特性,使用基于wait_for
或wait_until
的循环等待std::future
的结果时需要特别注意,例如下面的代码,可能永远卡在循环里:
using namespace std::literals; // for C++14 duration
// suffixes; see Item 34
void f() // f sleeps for 1 second,
{ // then returns
std::this_thread::sleep_for(1s);
}
auto fut = std::async(f); // run f asynchronously(conceptually)
while (fut.wait_for(100ms) != // loop until f has
std::future_status::ready) // finished running...
{ // which may never happen!
...
}
如果 f
的执行被 deferred
,那么 fut.wait_for()
将永远返回 std::future_status::deferred
。解决的方法是在进入该循环前加一个 if 判断。由于没有直接获取 std::future
的任务是否被延迟的方法,我们仍可以使用 wait_for
,将 timeout 设为 0 即可:
auto fut = std::async(f); // as above
if (fut.wait_for(0s) == // if task is
std::future_status::deferred) // deferred...
{
// ...use wait or get on fut
... // to call f synchronously
} else { // task isn't deferred
while (fut.wait_for(100ms) != // infinite loop not
std::future_status::ready) { // possible (assuming
// f finishes)
... // task is neither deferred nor ready,
// so do concurrent work until it's ready
}
... // fut is ready
}
- 当然,如果你确实想保证
std::async
的任务是被异步执行的,那么在构造时第一个参数传入std::launch::async
即可:
auto fut = std::async(std::launch::async, f); // launch f
// asynchronously
- 如果这种情况是常见的,你也可以做一层简单包装作为一个工具函数:
template<typename FuncType, typename... ArgTypes>
inline auto realAsync(FuncType&& f, ArgTypes&&... args)
{
return std::async(std::launch::async,
std::forward<FuncType>(f),
std::forward<ArgTypes>(args)...);
}
auto fut = realAsync(f);
总结
std::async
的默认运行策略同时允许运行同步和异步的运行。- 这种灵活性导致了访问
thread_local
变量时的不确定性(原文只是一笔带过,因此本篇中没有提及,有兴趣的读者自行查阅~),而且意味着任务可能永远不会被执行,影响了基于 timeout 的 wait 的逻辑。 - 如果任务的异步执行是必要的,(创建
std::async
时)指明std::launch::async
的运行策略。