C++11 异步操作

C++11 头文件 中包含了如下的类和函数:

    • Providers
      • promise
      • packaged_task
    • Futures
      • future
      • shared_future
  • 函数
    • async
  • 其它
    • future_error [class]
    • future_errc [enum class]
    • future_status [enum class]
    • launch [enum class]

std::promise

promise 用于实现实现线程同步

promise 对象可以存储一个指定类型为 T 的对象的值,该值可被 future 对象(可能存在另一个线程中)读取。

promise 在构造时与一个新的共享状态相关联,它们可以在该状态上存储一个指定类型 T 的对象或者从 std::exception 派生的异常。

promise 关联的共享状态可以通过成员函数 get_future 关联到一个 future 对象,在调用该函数之后这两个对象共享同一个共享状态

  • promise 对象是一个异步提供者,预计会在某个时刻为共享状态设置一个值。
  • future 对象是一个异步返回对象,它可以检索共享状态的值,并在必要的时刻等待它准备就绪。

共享状态的生命周期至少持续到与之相关联的最后一个对象将其释放或者销毁。

  • 成员函数

    (constructor)                   // 只有默认构造和移动构造,不能拷贝
    (destructor)    
    operator=                       // 禁止拷贝,只能移动
    get_future                      // 返回与对象的共享状态关联的 future 对象
    set_value                       // 设置共享状态的值为传入的值,并将共享状态设置为就绪
    set_exception
    set_value_at_thread_exit
    set_exception_at_thread_exit
    swap
    
  • 注意事项

    • 一个 promise 共享状态只能关联一个 future 对象,不能把多个 promise 对象关联到同一个 promise
    • 如果 promise 对象不设置值或者发生异常 promise 对象在析构时会自动设置一个 future_error 异常来设置自身的就绪状态
    • promise 对象的 set_value 方法只能被调用一次,多次调用会抛出 future_error 异常,这是因为第一次调用 set_value 后共享状态的状态就已经被设置为就绪
    • future 对象必须通过 std::promise::get_future 构造,否则无效。
  • 应用实例

    struct StudentInfo
    {
        std::string name;
        int age;
    };
    
    void SetValueThread(std::promise<StudentInfo>& prom)
    {
        // 设置共享状态的值为传入的值
        prom.set_value({ "Mike", 19 });
    }
    
    void GetValueThread(std::future<StudentInfo>& fut)
    {
        // 通过 future 对象获取共享的共享状态的值
        // 如果共享状态的状态不是 ready 则阻塞当前进程
        // 直到关联的 promise 对象使用 set_value 设置共享状态值为止
        auto stuInfo = fut.get();
    
        // 获得共享状态之后输出
        std::cout << "name: " << stuInfo.name << "\nage : " << stuInfo.age << std::endl;
    }
    
    int main()
    {
        std::future<StudentInfo> fut;
        std::promise<StudentInfo> prom;
        
        // 共享共享状态
        fut = prom.get_future();
    
        std::thread setThread(SetValueThread, std::ref(prom));
        std::thread getThread(GetValueThread, std::ref(fut));
    
        setThread.join();
        getThread.join();
    }
    
    /*
    // ---------------------------------------------------------
    // output
    // ---------------------------------------------------------
    name: Mike
    age : 19
    */
    

std::packaged_task

std::packaged_task 是一个包装器其作用和 std::function 类似: std::packaged_task 包装一个可调用对象但是允许异步获取该可调用对象产生的结果,该结果被传递给一个 std::future 对象。

std::packaged_task 对象内部包含了两个基本元素:

  • 被包装的任务:该任务是一个可调用对象.

  • 共享状态:用于保存被包装任务的返回值。可以通过 std::future::get_value() 方法进行访问。

  • 成员函数

    (constructor)                   // 只有默认构造和移动构造,不能拷贝
    (destructor)    
    valid                           // 检查对象是否拥有合法可调用对象
    operator=                       // 禁止拷贝,只能移动
    get_future                      // 返回与对象的共享状态关联的 future 对象
    operator()                      // 执行可调用对象
    make_ready_at_thread_exit       // 执行可调用函数,并保证结果在当前线程退出时为就绪状态
    reset                           // 重置状态,抛弃任何之前执行的存储结果
    swap
    
  • 注意事项
    [同 promise 类似]

  • 应用实例

    int countDown(int begin, int end)
    {
        // 每隔一秒输出一个数值
        for (int i = begin; i > end; --i)
        {
            std::cout << i << std::endl;
            std::this_thread::sleep_for(std::chrono::seconds(1));
        }
    
        std::cout << "count finished." << std::endl;
    
        return begin - end;
    }
    
    int main()
    {
        std::packaged_task<int(int, int)> pt(countDown);
    
        std::future<int> fut = pt.get_future();
    
        std::thread th(std::move(pt), 10, 0);
    
        int distance = fut.get();
    
        std::cout << distance << std::endl;
    
        th.join();
    }
    /*
    // ---------------------------------------------------------
    // output
    // ---------------------------------------------------------
    10
    9
    8
    7
    6
    5
    4
    3
    2
    1
    count finished.
    10
    */
    

std::future & std::shared_future

C++11 使用 std::future, std::shared_future 来获取异步任务的结果,可以认为是一种简单的线程同步手段。

std::future

std::future 通常由某个 Provider 创建,你可以把 Provider 想象成一个异步任务的提供者(生产-消费模型中的生产者),Provider 在某个线程中设置共享状态的值,与该共享状态相关联的 std::future 对象调用 get(通常在另外一个线程中) 获取该值。如果共享状态的标志不为 ready,则调用 std::future::get 会阻塞当前的调用者,直到 Provider 设置了共享状态的值(此时共享状态的标志变为 ready),std::future::get 返回异步任务的值或异常(如果发生了异常)。

  • 成员函数

    (constructor)               // 只有默认构造和移动构造,不能拷贝
    (destructor)                
    operator=                   // 禁止拷贝,只能移动
    share                       // 从当前对象中转移共享状态给 std::shared_future 并返回它
    get                         // 返回共享状态
    valid                       // 检查对象是否拥有共享状态
    wait                        // 等待结果就绪
    wait_for                    // 等待结果在指定时间内就绪
    wait_until                  // 等待结果在指定时间节点前就绪
    
  • 注意事项

    • 一个有效的 std::future 对象通常由以下 3 种 Provider 创建:

      • std::async: 该函数
      • std::promise::get_future: 该成员方法用于获取 std::promise 的 std::future 对象成员。
      • std::packaged_task::get_future: 该成员方法用于获取 std::packaged_task 的 std::future 对象成员。
    • 一个有效的 std::future 对象在调用成员函数 get 时会阻塞当前的调用线程,直到 Provider 设置了共享状态的值或者异常。一旦共享状态的状态编程就绪之后就会恢复线程释放共享状态,因此在调用 std::future::get() 之后不能再次调用。多次获取共享状态时可以使用 std::shared_future

  • 应用实例

    bool isPrime(int n)
    {
        for (int i = 2; i <= n / 2; ++i)
        {
            if (n % i == 0) return false;
        }
    
        return true;
    }
    
    int main()
    {
        std::future<bool> fut = std::async(isPrime, 444444443);
    
        std::cout << "checking..." << std::endl;
    
        std::chrono::microseconds span(100);
    
        while (fut.wait_for(span) == std::future_status::timeout)
            std::cout << ".";
    
        bool ret = fut.get();
    
        std::cout << "\n444444443 " << (ret ? "is" : "is not") << " prime." << std::endl;
    }
    /*
    // ---------------------------------------------------------
    // output
    // ---------------------------------------------------------
    checking...
    ...................................................
    444444443 is prime.
    */
    

std::shared_future

std::shared_future 对象的行为类似于 std::future 对象,除了它可以被复制,并且多个 shared_future 可以在共享状态结束时共享所有权。它们还允许在准备好后多次检索共享状态中的值

std::shared_future 对象可以从 std::future 对象隐式转换,或者通过调用 future::share 显式获取。在这两种情况下,从中获取它的未来对象将其与共享状态的关联转移到 shared_future 并变得无效。

  • 成员函数

    // 除了 std::future::share() std::future 中的方法 std::shared_future 都有实现
    (constructor)               // 可以拷贝构造
    (destructor)                
    operator=                   // 可以拷贝、移动
    get                         // 返回共享状态
    valid                       // 检查对象是否拥有共享状态
    wait                        // 等待结果就绪
    wait_for                    // 等待结果在指定时间内就绪
    wait_until                  // 等待结果在指定时间节点前就绪
    
  • 注意事项

  • 应用实例

std::async

std::async 函数原型如下:

template <class Fn, class... Args>
future<typename result_of<Fn(Args...)>::type>
async (Fn&& fn, Args&&... args);

template <class Fn, class... Args>
future<typename result_of<Fn(Args...)>::type>
async (launch policy, Fn&& fn, Args&&... args);

std::async 函数用于调用可执行对象 fn 在后台线程中执行并直接返回继续执行当前线程的任务。

std::async 的返回值可以通过返回的 std::future 对象访问。

std::async 可以指定可调用对象的执行策略,该执行策略是一个 syd::launch 类型的强枚举:

launch::async       // 启动一个新线程来调用 fn(就像一个线程对象是用 fn 和 args 作为参数构造的,并且访问返回的 future 的共享状态加入它)。
launch::deferred    // 对 fn 的调用被延迟,直到访问返回的未来的共享状态(使用 wait/wait_for/wait_until 或 get)。此时 fn 被调用并且该函数不再被视为延迟。当这个调用返回时,返回的未来的共享状态就准备好了。

该参数支持与操作: launch::async|launch::deferred 该函数自动选择策略(在某些时候)。这取决于系统和库实现,它们通常针对系统中当前的并发可用性进行优化。

应用实例:

using std::chrono::system_clock;

std::mutex mtx;

bool isPrime(int n)
{
    std::lock_guard<std::mutex> glck(mtx);

    std::time_t tt = system_clock::to_time_t(system_clock::now());
    struct std::tm* ptm = std::localtime(&tt);
    std::cout << "from sub  thread: " << std::put_time(ptm, "%X") << " now" << std::endl;

    for (int i = 2; i <= n / 2; ++i)
    {
        if (n % i == 0) return false;
    }

    return true;
}

int main()
{
    // 可调用对象立即执行
    std::future<bool> fut01 = std::async(std::launch::async, isPrime, 444444443);
    // 可调用对象延迟执行
    std::future<bool> fut02 = std::async(std::launch::deferred, isPrime, 444444443);

    mtx.lock();

    std::cout << "checking..." << std::endl;

    // 输出主线程的当前时间
    std::time_t tt = system_clock::to_time_t(system_clock::now());
    struct std::tm* ptm = std::localtime(&tt);
    std::cout << "from main thread: " << std::put_time(ptm, "%X") << " now" << std::endl;

    mtx.unlock();

    // 主线程休眠一段时间
    std::this_thread::sleep_for(std::chrono::seconds(5));

    bool ret01 = fut01.get();
    bool ret02 = fut02.get();

    std::cout << "444444443 " << (ret01 ? "is" : "is not") << " prime." << std::endl;
    std::cout << "444444443 " << (ret02 ? "is" : "is not") << " prime." << std::endl;
}
上一篇:C++并发编程之线程异步std::packaged_task知识点总结


下一篇:Qt之QFuture