0
点赞
收藏
分享

微信扫一扫

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的惰性求值策略,当调用者想要获取调用对象的执行结果时才开始执行调用对象。

​package_task​​​的优势更多是它能够初始化所有的可调用对象,并且支持对该调用对象的异步访问机制。
​​​promise​​更多的优势是线程之间的变量的传递,同时返回future类的共享状态。同时它也能够支持多种共享状态的访问机制,惰性求值/立即执行。

所以C++多线程的几个异步操作​​promise​​​、​​async​​​和​​package_task​​都可以进行线程之间的异步操作,希望以此笔记在今后多线程编程中各持所需进行学习。


举报

相关推荐

0 条评论