Skip to content

Latest commit

 

History

History
119 lines (89 loc) · 4.07 KB

File metadata and controls

119 lines (89 loc) · 4.07 KB

条款36:如果异步是必要的,则指定std::launch::async

假设函数 f 要传递给 std::async 以执行,则:

  • std::launch::async 启动策略意味着函数必须以异步方式运行,即在另一线程上执行。
  • std::launch::deferred 启动策略意味着函数只会在 std::async 所返回的期值的 getwait 得到调用时才执行。即执行会推迟至其中一个调用发生的时刻。当调用 getwait 时,f 会同步运行。即调用方会阻塞至 f 运行结束为止。如果 getwait 没有得到调用, f 是不会运行的。

std::async 的默认启动策略,是对上面二者或运算的结果。下面两个调用有着完全相同的意义:

auto fut1 = std::async(f);

auto fut2 = std::async(std::launch::async |
                       std::launch::deferred,
                       f);

这么一来,默认启动策略就允许 f 以异步或同步的方式运行皆可。这种弹性使得 std::async 与标准库的线程管理组件能够承担得起线程的创建和销毁、避免超定,以及负载均衡的责任。

但默认启动策略会带来一些问题,若给定线程 t 执行:

auto fut - std::async(f);

则:

  • 无法预知 f 是否会和 t 并发运行。
  • 无法预知 f 是否运行在与调用 futgetwait 函数的线程不同的某线程之上。
  • f 是否会运行都是无法预知的。

默认启动策略在调度上的弹性常会在使用 thread_local 变量时导致混淆。这意味着如果 f 读写此线程级局部存储(TLS)时,无法预知会取到的是哪个线程的局部存储。这也会影响哪些基于 wait 的循环中以超时为条件的。因为对任务调用 wait_for 或者 wait_until 会产生 std::launch::deferred 一值。

下面的循环貌似最终会停止,但实际上可能会永远运行下去:

using namespace std::literals;

void f() 
{
    std::this_thread::sleep_for(1s);
}

auto fut = std::async(f);

while (fut.wait_for(100ms) !=
       std::future_status::ready)
{
    ...
}

如果 f 与调用 std::async 的线程是并发执行的,这就没问题。如果 f 被推迟执行,则 fut.wait_for 终会返回 std::future_status::deferred,循环永远不会停止。

修正这个缺点并不难:

auto fut = std::async(f);

if (fut.wait_for(0s) ==
    std::future_status::deferred)
{
    // 使用fut的wait或get以异步方式调用f
} else {
    while (fut.wait_for(100ms) !=
           std::future_status::ready) 
    {
        // 任务被推迟,且未就绪
        // 则做并发工作,直至任务就绪
    }
}
// fut就绪

以默认启动策略对任务使用 std::async 能正常工作需要满足以下所有条件:

  • 任务不需要与调用 getwait 的线程并发执行。
  • 读写哪个下次的 thread_local 变量并无影响。
  • 或者可以给出保证在 std::async 返回的期值上调用 getwait ,或者可以接收任务可能永不执行。
  • 使用 wait_forwait_until 的代码会将任务被推迟的可能性纳入考量。

其中一个条件不满足,你就可能想要确保任务以异步执行。实现这一点的手法,就是指定 std::launch::async

auto fut = std::async(std::launch::async, f);

可以撰写一个函数,保证任务以异步方式执行,这是C++11实现版本:

template<typename F, typename... Ts>
inline
std::future<typename std::result_of<F(Ts...)>::type>
reallyAsync(F&& f, Ts&&... params)
{
    return std::async(std::launch::async,
                      std::forward<F>(f),
                      std::forward<Ts>(params)...);
}

C++14可以简化实现:

template<typename F, typename... Ts>
inline auto 
reallyAsync(F&& f, Ts&&... params)
{
    return std::async(std::launch::async,
                      std::forward<F>(f),
                      std::forward<Ts>(params)...);
}

reallyAsync 的用法就像 std::async

auto fut = reallyAsync(f);  // 以异步方式运行f