-
如上节所述,使用
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的运行策略。
本文深入探讨了C++标准库中的std::async函数及其运行策略,特别是std::launch::async和std::launch::deferred的区别。通过具体示例展示了如何确保任务的异步执行,并提供了一种实用的解决方案来避免因默认策略可能导致的任务延迟执行问题。
1161

被折叠的 条评论
为什么被折叠?



