上一讲《C++11 并发指南四(<future> 详解一 std::promise 介绍)》主要介绍了 <future> 头文件中的 std::promise 类,本文主要介绍 std::packaged_task。

std::packaged_task 包装一个可调用的对象,并且允许异步获取该可调用对象产生的结果,从包装可调用对象意义上来讲,std::packaged_task 与 std::function 类似,只不过 std::packaged_task 将其包装的可调用对象的执行结果传递给一个 std::future 对象(该对象通常在另外一个线程中获取 std::packaged_task 任务的执行结果)。

std::packaged_task 对象内部包含了两个最基本元素,一、被包装的任务(stored task),任务(task)是一个可调用的对象,如函数指针、成员函数指针或者函数对象,二、共享状态(shared state),用于保存任务的返回值,可以通过 std::future 对象来达到异步访问共享状态的效果。

可以通过 std::packged_task::get_future 来获取与共享状态相关联的 std::future 对象。在调用该函数之后,两个对象共享相同的共享状态,具体解释如下:

  • std::packaged_task 对象是异步 Provider,它在某一时刻通过调用被包装的任务来设置共享状态的值。
  • std::future 对象是一个异步返回对象,通过它可以获得共享状态的值,当然在必要的时候需要等待共享状态标志变为 ready.

std::packaged_task 的共享状态的生命周期一直持续到最后一个与之相关联的对象被释放或者销毁为止。下面一个小例子大致讲了 std::packaged_task 的用法:

  1. #include <iostream> // std::cout
  2. #include <future> // std::packaged_task, std::future
  3. #include <chrono> // std::chrono::seconds
  4. #include <thread> // std::thread, std::this_thread::sleep_for
  5.  
  6. // count down taking a second for each value:
  7. int countdown (int from, int to) {
  8. for (int i=from; i!=to; --i) {
  9. std::cout << i << '\n';
  10. std::this_thread::sleep_for(std::chrono::seconds());
  11. }
  12. std::cout << "Finished!\n";
  13. return from - to;
  14. }
  15.  
  16. int main ()
  17. {
  18. std::packaged_task<int(int,int)> task(countdown); // 设置 packaged_task
  19. std::future<int> ret = task.get_future(); // 获得与 packaged_task 共享状态相关联的 future 对象.
  20.  
  21. std::thread th(std::move(task), , ); //创建一个新线程完成计数任务.
  22.  
  23. int value = ret.get(); // 等待任务完成并获取结果.
  24.  
  25. std::cout << "The countdown lasted for " << value << " seconds.\n";
  26.  
  27. th.join();
  28. return ;
  29. }

执行结果为:

  1. concurrency ) ./Packaged_Task1
  2.  
  3. Finished!
  4. The countdown lasted for seconds.

std::packaged_task 构造函数

default (1)
  1. packaged_task() noexcept;
initialization (2)
  1. template <class Fn>
  2. explicit packaged_task (Fn&& fn);
with allocator (3)
  1. template <class Fn, class Alloc>
  2. explicit packaged_task (allocator_arg_t aa, const Alloc& alloc, Fn&& fn);
copy [deleted] (4)
  1. packaged_task (const packaged_task&) = delete;
move (5)
  1. packaged_task (packaged_task&& x) noexcept;

std::packaged_task 构造函数共有 5 中形式,不过拷贝构造已经被禁用了。下面简单地介绍一下上述几种构造函数的语义:

  1. 默认构造函数,初始化一个空的共享状态,并且该 packaged_task 对象无包装任务。
  2. 初始化一个共享状态,并且被包装任务由参数 fn 指定。
  3. 带自定义内存分配器的构造函数,与默认构造函数类似,但是使用自定义分配器来分配共享状态。
  4. 拷贝构造函数,被禁用。
  5. 移动构造函数。

下面例子介绍了各类构造函数的用法:

  1. #include <iostream> // std::cout
  2. #include <utility> // std::move
  3. #include <future> // std::packaged_task, std::future
  4. #include <thread> // std::thread
  5.  
  6. int main ()
  7. {
  8. std::packaged_task<int(int)> foo; // 默认构造函数.
  9.  
  10. // 使用 lambda 表达式初始化一个 packaged_task 对象.
  11. std::packaged_task<int(int)> bar([](int x){return x*;});
  12.  
  13. foo = std::move(bar); // move-赋值操作,也是 C++11 中的新特性.
  14.  
  15. // 获取与 packaged_task 共享状态相关联的 future 对象.
  16. std::future<int> ret = foo.get_future();
  17.  
  18. std::thread(std::move(foo), ).detach(); // 产生线程,调用被包装的任务.
  19.  
  20. int value = ret.get(); // 等待任务完成并获取结果.
  21. std::cout << "The double of 10 is " << value << ".\n";
  22.  
  23. return ;
  24. }

与 std::promise 类似, std::packaged_task 也禁用了普通的赋值操作运算,只允许 move 赋值运算。

std::packaged_task::valid 介绍

检查当前 packaged_task 是否和一个有效的共享状态相关联,对于由默认构造函数生成的 packaged_task 对象,该函数返回 false,除非中间进行了 move 赋值操作或者 swap 操作。

请看下例:

  1. #include <iostream> // std::cout
  2. #include <utility> // std::move
  3. #include <future> // std::packaged_task, std::future
  4. #include <thread> // std::thread
  5.  
  6. // 在新线程中启动一个 int(int) packaged_task.
  7. std::future<int> launcher(std::packaged_task<int(int)>& tsk, int arg)
  8. {
  9. if (tsk.valid()) {
  10. std::future<int> ret = tsk.get_future();
  11. std::thread (std::move(tsk),arg).detach();
  12. return ret;
  13. }
  14. else return std::future<int>();
  15. }
  16.  
  17. int main ()
  18. {
  19. std::packaged_task<int(int)> tsk([](int x){return x*;});
  20.  
  21. std::future<int> fut = launcher(tsk,);
  22.  
  23. std::cout << "The double of 25 is " << fut.get() << ".\n";
  24.  
  25. return ;
  26. }

std::packaged_task::get_future 介绍

返回一个与 packaged_task 对象共享状态相关的 future 对象。返回的 future 对象可以获得由另外一个线程在该 packaged_task 对象的共享状态上设置的某个值或者异常。

请看例子(其实前面已经讲了 get_future 的例子):

  1. #include <iostream> // std::cout
  2. #include <utility> // std::move
  3. #include <future> // std::packaged_task, std::future
  4. #include <thread> // std::thread
  5.  
  6. int main ()
  7. {
  8. std::packaged_task<int(int)> tsk([](int x) { return x * ; })); // package task
  9.  
  10. std::future<int> fut = tsk.get_future(); // 获取 future 对象.
  11.  
  12. std::thread(std::move(tsk), ).detach(); // 生成新线程并调用packaged_task.
  13.  
  14. int value = fut.get(); // 等待任务完成, 并获取结果.
  15.  
  16. std::cout << "The triple of 100 is " << value << ".\n";
  17.  
  18. return ;
  19. }

std::packaged_task::operator()(Args... args) 介绍

调用该 packaged_task 对象所包装的对象(通常为函数指针,函数对象,lambda 表达式等),传入的参数为 args. 调用该函数一般会发生两种情况:

  • 如果成功调用 packaged_task 所包装的对象,则返回值(如果被包装的对象有返回值的话)被保存在 packaged_task 的共享状态中。
  • 如果调用 packaged_task 所包装的对象失败,并且抛出了异常,则异常也会被保存在 packaged_task 的共享状态中。

以上两种情况都使共享状态的标志变为 ready,因此其他等待该共享状态的线程可以获取共享状态的值或者异常并继续执行下去。

共享状态的值可以通过在 future 对象(由 get_future获得)上调用 get 来获得。

由于被包装的任务在 packaged_task 构造时指定,因此调用 operator() 的效果由 packaged_task 对象构造时所指定的可调用对象来决定:

  • 如果被包装的任务是函数指针或者函数对象,调用 std::packaged_task::operator() 只是将参数传递给被包装的对象。
  • 如果被包装的任务是指向类的非静态成员函数的指针,那么 std::packaged_task::operator() 的第一个参数应该指定为成员函数被调用的那个对象,剩余的参数作为该成员函数的参数。
  • 如果被包装的任务是指向类的非静态成员变量,那么 std::packaged_task::operator() 只允许单个参数。

std::packaged_task::make_ready_at_thread_exit 介绍

该函数会调用被包装的任务,并向任务传递参数,类似 std::packaged_task 的 operator() 成员函数。但是与 operator() 函数不同的是,make_ready_at_thread_exit 并不会立即设置共享状态的标志为 ready,而是在线程退出时设置共享状态的标志。

如果与该 packaged_task 共享状态相关联的 future 对象在 future::get 处等待,则当前的 future::get 调用会被阻塞,直到线程退出。而一旦线程退出,future::get 调用继续执行,或者抛出异常。

注意,该函数已经设置了 promise 共享状态的值,如果在线程结束之前有其他设置或者修改共享状态的值的操作,则会抛出 future_error( promise_already_satisfied )。

std::packaged_task::reset() 介绍

重置 packaged_task 的共享状态,但是保留之前的被包装的任务。请看例子,该例子中,packaged_task 被重用了多次:

  1. #include <iostream> // std::cout
  2. #include <utility> // std::move
  3. #include <future> // std::packaged_task, std::future
  4. #include <thread> // std::thread
  5.  
  6. // a simple task:
  7. int triple (int x) { return x*; }
  8.  
  9. int main ()
  10. {
  11. std::packaged_task<int(int)> tsk (triple); // package task
  12.  
  13. std::future<int> fut = tsk.get_future();
  14. std::thread (std::move(tsk), ).detach();
  15. std::cout << "The triple of 100 is " << fut.get() << ".\n";
  16.  
  17. // re-use same task object:
  18. tsk.reset();
  19. fut = tsk.get_future();
  20. std::thread(std::move(tsk), ).detach();
  21. std::cout << "Thre triple of 200 is " << fut.get() << ".\n";
  22.  
  23. return ;
  24. }

std::packaged_task::swap() 介绍

交换 packaged_task 的共享状态。

好了,std::packaged_task 介绍到这里,本文参考了 http://www.cplusplus.com/reference/future/packaged_task/ 相关的内容。后一篇文章我将向大家介绍 std::future,std::shared_future 以及 std::future_error,另外还会介绍 <future> 头文件中的 std::async,std::future_category 函数以及相关枚举类型。

C++11 并发指南四(<future> 详解二 std::packaged_task 介绍)的更多相关文章

  1. C++11 并发指南四(<future> 详解一 std::promise 介绍)

    前面两讲<C++11 并发指南二(std::thread 详解)>,<C++11 并发指南三(std::mutex 详解)>分别介绍了 std::thread 和 std::m ...

  2. C++11 并发指南四(<future> 详解一 std::promise 介绍)(转)

    前面两讲<C++11 并发指南二(std::thread 详解)>,<C++11 并发指南三(std::mutex 详解)>分别介绍了 std::thread 和 std::m ...

  3. C++11 并发指南四(<future> 详解三 std::future & std::shared_future)

    上一讲<C++11 并发指南四(<future> 详解二 std::packaged_task 介绍)>主要介绍了 <future> 头文件中的 std::pack ...

  4. C++11 并发指南四(<future> 详解三 std::future & std::shared_future)(转)

    上一讲<C++11 并发指南四(<future> 详解二 std::packaged_task 介绍)>主要介绍了 <future> 头文件中的 std::pack ...

  5. C++11 并发指南三(Lock 详解)(转载)

    multithreading 多线程 C++11 C++11多线程基本使用 C++11 并发指南三(Lock 详解) 在 <C++11 并发指南三(std::mutex 详解)>一文中我们 ...

  6. C++11 并发指南三(Lock 详解)

    在 <C++11 并发指南三(std::mutex 详解)>一文中我们主要介绍了 C++11 标准中的互斥量(Mutex),并简单介绍了一下两种锁类型.本节将详细介绍一下 C++11 标准 ...

  7. C++11 并发指南六( <atomic> 类型详解二 std::atomic )

    C++11 并发指南六(atomic 类型详解一 atomic_flag 介绍)  一文介绍了 C++11 中最简单的原子类型 std::atomic_flag,但是 std::atomic_flag ...

  8. 【C/C++开发】C++11 并发指南三(std::mutex 详解)

    本系列文章主要介绍 C++11 并发编程,计划分为 9 章介绍 C++11 的并发和多线程编程,分别如下: C++11 并发指南一(C++11 多线程初探)(本章计划 1-2 篇,已完成 1 篇) C ...

  9. C++11 并发指南系列

    本系列文章主要介绍 C++11 并发编程,计划分为 9 章介绍 C++11 的并发和多线程编程,分别如下: C++11 并发指南一(C++11 多线程初探)(本章计划 1-2 篇,已完成 1 篇) C ...

随机推荐

  1. 一句SQL完成动态分级查询

    在最近的活字格项目中使用ActiveReports报表设计器设计一个报表模板时,遇到一个多级分类的难题:需要将某个部门所有销售及下属部门的销售金额汇总,因为下属级别的层次不确定,所以靠拼接子查询的方式 ...

  2. 自定义View的三个构造函数

    自定义View有三个构造方法,它们的作用是不同的. public MyView(Context context) { super(context); } public MyView(Context c ...

  3. NoHttp封装--08 用一个实体类接收所有接口数据

    1.用户信息获取--bean实体类形式返回数据 ①服务器端: 代码: protected void onHandler(HttpServletRequest request, HttpServletR ...

  4. java----自动类型转换

  5. hive Caused by: java.lang.RuntimeException: Unable to instantiate org.apache.hadoop.hive.ql.metadata.SessionHiveMetaStoreClient

    Exception in thread "main" java.lang.RuntimeException: org.apache.hadoop.hive.ql.metadata. ...

  6. hadoop java上传文件

    import java.io.BufferedInputStream; import java.io.FileInputStream; import java.io.InputStream; impo ...

  7. [20190213]测试服务端打开那些端口.txt

    [20190213]测试服务端打开那些端口.txt --//前几天测试使用发送信息到/dev/tcp/ip_address/port,测试端口是否打开.写简单写一个脚本验证看看. $ seq 1 65 ...

  8. C# 实体/集合差异比较,比较两个实体或集合值是否一样,将实体2的值动态赋值给实体1(名称一样的属性进行赋值)

    /// <summary> /// 实体差异比较器 /// </summary> /// <param name="source">源版本实体& ...

  9. 洗礼灵魂,修炼python(67)--爬虫篇—cookielib之爬取需要账户登录验证的网站

    学完前面的教程,相信你已经能爬取大部分的网站信息了,但是当你爬的网站多了,你应该会发现一个新问题,有的网站需要登录账户才能看到更多的信息对吧?那么这种网站怎么爬取呢?这些登录数据就是今天要说的——co ...

  10. JavaScript获取IE版本号与HTML设置ie文档模式

    JavaScript获取IE版本代码: var gIE = getIE(); alert(gIE.version) function getIE() { var rmsie = /(msie) ([\ ...