• Item 36: Specify std::launch::async if asynchronicity is essential.


    Item 36: Specify std::launch::async if asynchronicity is essential.

    当你使用 std::async() 执行一个函数或可调用对象时,你通常期望这个函数是异步执行。但是, std::async() 不一定如你所愿。其实 std::async() 是根据执行策略决定是否会异步执行。 std::async() 有两种执行策略,定义在 std::launch 作用域中:

    • std::launch::async 函数或可执行对象必须异步执行,也即运行在其他线程上。
    • std::launch::deferred 函数或可执行对象延迟执行。仅在 std::async() 的返回对象 std::future 调用 getwait 时,才在当前线程同步执行,并且调用者会阻塞直到函数执行完成。

    std::async() 的默认策略其实是二者的组合,也即以下两者涵义完全相同:

    auto fut1 = std::async(f); // run f using default launch policy
    
    auto fut2 = std::async(std::launch::async |   // run f either
                           std::launch::deferred, // async or
                           f);                    // deferred
    
    • 1
    • 2
    • 3
    • 4
    • 5

    默认的策略下,f 可能是同步执行也可能是异步执行。正如 Item 35: Prefer task-based programming to thread-based. 中讨论的:标准库的线程管理模块承担了线程的创建和释放的职责,可以有效避免超额订阅、保证负载均衡。这极大地方便了 std::async 的使用。

    但是,默认策略也会有如下问题:

    • 无法预测 f 是否是并发执行。
    • 无法预测 f 是否运行在 getwait 调用时的线程上。
    • 甚至无法预测 f 是否已经执行了。因为没法保证一定会调用 getwait

    f 要访问本地线程存储(TLS,Thread Local Storage)时,无法预测访问的是哪个线程的本地存储。

    auto fut = std::async(f); // TLS for f possibly for
                              // independent thread, but
                              // possibly for thread
                              // invoking get or wait on fut
    
    • 1
    • 2
    • 3
    • 4

    std::async 的默认策略还会影响到 wait_for 超时调用写法,可能导致 bug,例如:

    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!}
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11

    如果 std::async 是并发执行,也即执行策略为 std::launch::async,以上代码没有问题。但是,如果执行策略为 std::launch::deferred时,fut.wait_for 总是返回 future_status::deferred,以上代码就会有问题。解决办法也很简单,先通过 wait_for 的超时时间为 0 来检测 std::async 是异步执行还是同步执行:

    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
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16

    综上,如果你的使用场景不是以下几种,则需要考虑是否需要替换 std::async 的默认策略:

    • 当调用 getwait 时,任务不需要并发执行。
    • 并不关心访问的是哪个线程的本地存储。
    • 可以保证 getwait 一定会被调用,或者任务不被执行也能接受。
    • 使用 wait_forwait_until 时,需要考虑 std::launch::deferred 策略。

    如果不是以上场景,你可能需要指定使用 std::launch::async 策略,也即真正创建一个线程去并发执行任务:

    auto fut = std::async(std::launch::async, f);  // launch f asynchronously
    
    • 1

    这里提供一个并发执行任务的封装:

    template<typename F, typename... Ts>  // C++11
    inline
    std::future<typename std::result_of<F(Ts...)>::type>
    reallyAsync(F&& f, Ts&&... params)       // return future
    {                                        // for asynchronous
      return std::async(std::launch::async,  // call to f(params...)
                        std::forward<F>(f),
                        std::forward<Ts>(params)...);
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9

    reallyAsync 接受一个可执行对象 f 和 多个参数 params,并完美转发给 std::async ,同时使用 std::launch::async 策略。C++14 版本如下:

    template<typename F, typename... Ts>
    inline
    auto     // C++14
    reallyAsync(F&& f, Ts&&... params)
    {
      return std::async(std::launch::async,
      std::forward<F>(f),
      std::forward<Ts>(params)...);
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9

    至此,本文结束。

    参考:

  • 相关阅读:
    2023-9-25 货仓选址
    Rust嵌入式编程---panic处理和异常处理
    面试宝典之C++多态灵魂拷问
    20.1CubeMx配置FMC控制SDRAM【W9825G6KH-6】
    Python安装pycrypto出错处理方法
    4.CentOS7安装MySQL5.7
    国庆day1
    TMI4054H锂电池充电管理IC
    MATLAB使用绘图plot制作动态GIF
    Reat 中的 useTransition 钩子函数
  • 原文地址:https://blog.csdn.net/Dong_HFUT/article/details/126076160