天天看点

C++多线程:package_task异步调用任何目标执行操作

文章目录

  • ​​描述​​
  • ​​函数成员及使用​​
  • ​​总结​​

我们上一篇描述关于C++多线程中的异步操作相关库(

​async​

​和

​promise​

​),本节将分享c++标准库中最后一个多线程异步操作库

​package_task​

​的学习笔记。

描述

  • 头文件 ​

    ​<future>​

  • 声明方式: ​

    ​template< class R, class ...Args > class packaged_task<R(Args...)>;​

  • 简介

    ​package_task​

    ​标准类模版包装了任何可调用的目标,其中包括函数,​

    ​std::bind​

    ​表达式,​

    ​lamda​

    ​表达式或者其他函数对象。并支持​

    ​package_task​

    ​对象调用者的异步调用。调用之后的返回值或者产生的异常能够被存储在能够被​

    ​std::future​

    ​对象访问的共享状态中。

    综上描述,我们很明显能够体会到该模版类提供的功能和​

    ​promise​

    ​类非常接近,但是​

    ​promise​

    ​类没有办法初始化所有可调用的对象,promise类仅提供共享状态的多种访问机制并提供线程之间变量的共享机制。

函数成员及使用

  • 构造函数

    ​packaged_task() noexcept;​

    ​构造无任务且无共享状态的​

    ​package_task​

    ​对象

    ​template <class F> explicit packaged_task( F&& f )​

    ​ 构造拥有共享状态和任务副本的 std::packaged_task 对象,

    ​packaged_task( const packaged_task& ) = delete;​

    ​复制构造函数被删除, std::packaged_task 仅可移动

    ​packaged_task( packaged_task&& rhs ) noexcept;​

    ​ rhs 之前所占有的共享状态和任务构造 std::packaged_task ,令 rhs 留在无共享状态且拥有被移动后的任务的状态

    查看如下代码:

#include <future>
#include <iostream>
#include <thread>
 
int fib(int n)
{
    if (n < 3) return 1;
    else 
    {
        std::cout << "fib  result " << (n-1)+(n-2) << std::endl;
        return (n-1) + (n-2);
    }
}
 
int main()
{
    std::packaged_task<int(int)> fib_task(&fib); 
 
    std::cout << "starting task\n";
    //此时已经将package_task调用对象的执行返回值转交给future对象,所以后续
    //的线程执行由惰性赋值来触发。即当future的对象的共享状态尝试获取线程执行
    //结果的时候才进行线程执行,并返回结果。类似std::async的policy:std::launch::deferred
    auto result = fib_task.get_future(); 
    std::thread t(std::move(fib_task), 40);
 
    std::cout << "waiting for task to finish...\n";
    std::cout << result.get() << '\n';
 
    std::cout << "task complete\n";
    t.join();
}      

输出如下:

starting task
waiting for task to finish...
fib  result 77
77
task complete      
  • 析构函数​

    ​~packaged_task()​

    ​ 抛弃共享状态并销毁存储的任务对象,同 std::promise::~promise ,若在令共享状态就绪前抛弃它,则存储以 std::future_errc::broken_promise 为 error_code 的 std::future_error 异常
  • 赋值运算符

    ​​

    ​packaged_task& operator=( const packaged_task& ) = delete​

    ​ 复制赋值运算符被删除, std::packaged_task 仅可移动
  • ​std::packaged_task<R(Args...)>::get_future​

    ​​返回与 *this 共享同一共享状态的 future

    同​​

    ​promise​

    ​​类一样,get_future 只能对每个 packaged_task 调用一次

    ​​

    ​get_future​

    ​成员出现异常的情况如下:
  • 已通过调用 get_future 取得共享状态。设置 error_category 为 future_already_retrieved
  • *this 无共享状态。设置 error_category 为 no_state
  • ​std::packaged_task<R(Args...)>::make_ready_at_thread_exit​

    ​​成员函数

    以转发的 args 为参数调用存储的任务。任务返回值或任何抛出的异常被存储于 *this 的共享状态。

    仅在当前线程退出,并销毁所有线程局域存储期对象后,才令共享状态就绪

    代码如下

#include <future>
#include <iostream>
#include <chrono>
#include <thread>
#include <functional>
#include <utility>
 
void worker(std::future<void>& output)
{
    std::packaged_task<void(bool&)> my_task{ [](bool& done) { done=true; } };
 
    auto result = my_task.get_future();
 
    bool done = false;
 
    //根据打印已经可以看到,此时已经执行了package_task的线程内容
    //但是共享状态到函数作用域结束之前并未就绪
    my_task.make_ready_at_thread_exit(done); // 立即执行任务
 
    std::cout << "worker: done = " << std::boolalpha << done << std::endl;
 
    auto status = result.wait_for(std::chrono::seconds(0));
    if (status == std::future_status::timeout)
        std::cout << "worker: result is not ready yet" << std::endl;
 
    output = std::move(result);
}
 
 
int main()
{
    std::future<void> result;
 
    std::thread{worker, std::ref(result)}.join();
 
    //等到线程函数返回结果,且future对象就绪之后,可以看到打印状态变为就绪
    auto status = result.wait_for(std::chrono::seconds(0));
    if (status == std::future_status::ready)
        std::cout << "main: result is ready" << std::endl;
}      

总结

综上对​

​package_task​

​​的描述,我们可以看到package_task模版类就像​

​promise​

​​一样可以被异步调用,并且将调用对象执行的结果封装在​

​future​

​​的共享状态中,来让调用者获取。同时​

​package_task​

​的调用者获取调用对象的执行结果过程就像async的惰性求值策略,当调用者想要获取调用对象的执行结果时才开始执行调用对象。