std::future

其实future有两个兄弟,一个是std::future, 一个是它大哥std::shared_future。他们的区别就是std::future只支持移动语义,它所引用的共享状态不与另一异步返回对象共享。换成人话就是如果你想在多个线程共享一个future,那么你应该用std::shared_future,换成大白话就是你要是想多个线程等待一个future,那么你应该用std::shared_future。如果还是不明白,文章最后会给一个小例子说明。这篇文章主要讲的是std::future。

同时讲std::future离不开它的承载者和创建者, 也就是std::async , std::packaged_task,std::promise,会在今后的文章一一描述。

首先我们先看std::future是如何定义的(如果感觉枯燥可略过这节):

定义于头文件 <future>    

template< class T > class future;

(1) (C++11 起)

template< class T > class future<T&>;

(2) (C++11 起)

template<>          class future<void>;

(3) (C++11 起)

类模板 std::future 提供访问异步操作结果的机制:

  • (通过 std::async 、 std::packaged_task 或 std::promise 创建的)异步操作能提供一个 std::future 对象给该异步操作的创建者(线程)。

  • 然后,异步操作的创建者能用各种方法查询、等待或从 std::future 提取值。若异步操作仍未提供值,则这些方法可能阻塞。

  • 异步操作准备好发送结果给创建者时,它(异步操作)能通过修改链接到创建者的 std::future 的共享状态(例如 std::promise::set_value )进行。

成员函数

(构造函数)

构造 future 对象 (公开成员函数)

(析构函数)

析构 future 对象 (公开成员函数)

operator=

移动future对象 (公开成员函数)

share

从 *this 转移共享状态给 shared_future 并返回它 
(公开成员函数)

获取结果

get

返回结果 (公开成员函数)

状态

valid

检查 future 是否拥有共享状态 (公开成员函数)

wait

等待结果变得可用 (公开成员函数)

wait_for

等待结果,如果在指定的超时间隔后仍然无法得到结果,则返回。(公开成员函数)

wait_until

等待结果,如果在已经到达指定的时间点时仍然无法得到结果,则返回。(公开成员函数)

std::future 涉及到了并行编程的概念。为了更好的理解,我们先讨论一个我们会经常遇到的场景。现在你要做三件事,一个是炖排骨一个是烧开水,同时你还在追最火的电视剧。那么作为一个正常人,你不会傻傻的先烧开水,等水烧完了炖排骨,等排骨好了再去看电视剧吧。一个正常人大概率会选择炖上排骨烧上水后就去看电视剧,然后不断的查看排骨和水是不是好了。

当然排骨和水的处理方式不一样,烧热水等开了会响,而排骨就要根据个人经验还有排骨当前的状态放入佐料并且控制时间。这就涉及到我们编程中的架构模型,std::future要讨论的就是排骨这种情况,而不是烧水。

在编程的过程中,程序中往往会有一些功能或者很耗时或者很占计算资源,这样的程序我们往往倾向于让它在另一个thread中运行,我们的主thread可以先干其他事情,等干完了其他事情在来看看之前的工作干完了没有,如果干完了,那么拿出结果,如果没干完就等它干完或者只等一会。

下边就举一个例子:

#include <iostream>
#include <future>
#include <thread>
#include <chrono>int main()
{std::future<int> future = std::async(std::launch::async, [](){ std::this_thread::sleep_for(std::chrono::seconds(3));return 8;  }); std::cout << "开始炖排骨...\n";std::future_status status;do {// 干点别的活,比如看电视剧status = future.wait_for(std::chrono::seconds(1));if (status == std::future_status::deferred) {std::cout << "deferred\n";} else if (status == std::future_status::timeout) {std::cout << "一分钟以后排骨还没好\n";} else if (status == std::future_status::ready) {std::cout << "排骨好了!\n";}} while (status != std::future_status::ready); std::cout << "result is " << future.get() << '\n';
}

可能的输出:

开始炖排骨...一分钟以后排骨还没好一分钟以后排骨还没好排骨好了!result is 8

当然在实践中我们不会死等,我们会隔一会看看排骨好没好。

std::shared_future

类模板 std::shared_future 提供访问异步操作结果的机制,类似 std::future ,除了允许多个线程等候同一共享状态。不同于仅可移动的 std::future (故只有一个实例能指代任何特定的异步结果),std::shared_future 可复制而且多个 shared_future 对象能指代同一共享状态。

若每个线程通过其自身的 shared_future 对象副本访问,则从多个线程访问同一共享状态是安全的。

shared_future 不是文章的重点,这里仅仅举一个例子说明:

#include <iostream>
#include <future>
#include <chrono>int main()
{   std::promise<void> ready_promise, t1_ready_promise, t2_ready_promise;std::shared_future<void> ready_future(ready_promise.get_future());std::chrono::time_point<std::chrono::high_resolution_clock> start;auto fun1 = [&, ready_future]() -> std::chrono::duration<double, std::milli> {t1_ready_promise.set_value();ready_future.wait(); // 等待来自 main() 的信号return std::chrono::high_resolution_clock::now() - start;};auto fun2 = [&, ready_future]() -> std::chrono::duration<double, std::milli> {t2_ready_promise.set_value();ready_future.wait(); // 等待来自 main() 的信号return std::chrono::high_resolution_clock::now() - start;};auto result1 = std::async(std::launch::async, fun1);auto result2 = std::async(std::launch::async, fun2);// 等待线程变为就绪t1_ready_promise.get_future().wait();t2_ready_promise.get_future().wait();// 线程已就绪,开始时钟start = std::chrono::high_resolution_clock::now();// 向线程发信 使之运行ready_promise.set_value();std::cout << "Thread 1 received the signal "<< result1.get().count() << " ms after start\n"<< "Thread 2 received the signal "<< result2.get().count() << " ms after start\n";
}

可能的输出:

Thread 1 received the signal 0.072 ms after startThread 2 received the signal 0.041 ms after start

std::promise

promise 对象可以保存某一类型 T 的值,该值可被 future 对象读取(可能在另外一个线程中),因此 promise 也提供了一种线程同步的手段。在 promise 对象构造时可以和一个共享状态(通常是std::future)相关联,并可以在相关联的共享状态(std::future)上保存一个类型为 T 的值。

可以通过 get_future 来获取与该 promise 对象相关联的 future 对象,调用该函数之后,两个对象共享相同的共享状态(shared state)

promise 对象是异步 Provider,它可以在某一时刻设置共享状态的值。

future 对象可以异步返回共享状态的值,或者在必要的情况下阻塞调用者并等待共享状态标志变为 ready,然后才能获取共享状态的值。

下面我们来看看官方定义:

定义于头文件 <future>    

template< class R > class promise;

(1) (C++11 起)

template< class R > class promise<R&>;

(2) (C++11 起)

template<>          class promise<void>;

(3) (C++11 起)

1) 空模板

2) 非 void 特化,用于在线程间交流对象

3) void 特化,用于交流无状态事件

类模板 std::promise 提供存储值或异常的设施,之后通过 std::promise 对象所创建的 std::future 对象异步获得结果。注意 std::promise 只应当使用一次。

每个 promise 与共享状态关联,共享状态含有一些状态信息和可能仍未求值的结果,它求值为值(可能为 void )或求值为异常。promise 可以对共享状态做三件事:

  • 使就绪:promise 存储结果或异常于共享状态。标记共享状态为就绪,并解除阻塞任何等待于与该共享状态关联的 future 上的线程。

  • 释放:promise 放弃其对共享状态的引用。若这是最后一个这种引用,则销毁共享状态。除非这是 std::async 所创建的未就绪的共享状态,否则此操作不阻塞。

  • 抛弃:promise 存储以 std::future_errc::broken_promise 为 error_code 的 std::future_error 类型异常,令共享状态为就绪,然后释放它。

promise 是 promise-future 交流通道的“推”端:存储值于共享状态的操作同步于(定义于 std::memory_order )任何在共享状态上等待的函数(如 std::future::get )的成功返回。其他情况下对共享状态的共时访问可能冲突:例如, std::shared_future::get 的多个调用方必须全都是只读,或提供外部同步。

成员函数

(构造函数)

构造std::promise对象 
(公开成员函数)

(析构函数)

析构std::promise对象 
(公开成员函数)

operator=

赋值共享状态 
(公开成员函数)

swap

交换二个 promise 对象 
(公开成员函数)

获取结果

get_future

返回与承诺的结果关联的 future 
(公开成员函数)

设置结果

set_value

设置结果为指定值 
(公开成员函数)

set_value_at_thread_exit

设置结果为指定值,同时仅在线程退出时分发提醒 
(公开成员函数)

set_exception

设置结果为指示异常 
(公开成员函数)

set_exception_at_thread_exit

设置结果为指示异常,同时仅在线程退出时分发提醒 
(公开成员函数)

下面举一个小例子,将promise<int> 用作线程间信号:

#include <vector>#include <thread>#include <future>#include <numeric>#include <iostream>#include <chrono> void accumulate(std::vector<int>::iterator first,                std::vector<int>::iterator last,                std::promise<int> accumulate_promise){    int sum = std::accumulate(first, last, 0);    accumulate_promise.set_value(sum);  // 提醒 future} void do_work(std::promise<void> barrier){    std::this_thread::sleep_for(std::chrono::seconds(1));    barrier.set_value();} int main(){    // 演示用 promise<int> 在线程间传递结果。    std::vector<int> numbers = { 1, 2, 3, 4, 5, 6 };    std::promise<int> accumulate_promise;    std::future<int> accumulate_future = accumulate_promise.get_future();    std::thread work_thread(accumulate, numbers.begin(), numbers.end(),                            std::move(accumulate_promise));     // future::get() 将等待直至该 future 拥有合法结果并取得它    // 无需在 get() 前调用 wait()    //accumulate_future.wait();  // 等待结果    std::cout << "result=" << accumulate_future.get() << '\n';    work_thread.join();  // wait for thread completion     // 演示用 promise<void> 在线程间对状态发信号    std::promise<void> barrier;    std::future<void> barrier_future = barrier.get_future();    std::thread new_work_thread(do_work, std::move(barrier));    barrier_future.wait();    new_work_thread.join();}

输出:

result=21

std::packaged_task

其实std::packaged_task并没有引入新的概念, 它是一个包装类,它包装任何可调用 (Callable) 目标(函数、 lambda 表达式、 bind 表达式或其他函数对象),使得能异步调用它。其返回值或所抛异常被存储于能通过 std::future 对象访问的共享状态中。 

如 std::function , std::packaged_task 是多态、具分配器的容器:可在堆上或以提供的分配器分配存储的可调用对象。

std::packaged_task本身很简单,但是却给了我们很多想象空间,这篇文章就不展开讲它的具体应用场景。

下面我们来看一下具体的定义:

定义于头文件 <future>

   

template< class > class packaged_task; // 不定义

(1) (C++11 起)

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

(2) (C++11 起)

下边是一些比较枯燥的API解释, 不喜欢看可以略过,直接看例子。

成员函数

(构造函数)

构造任务对象 
(公开成员函数)

(析构函数)

析构任务对象 
(公开成员函数)

operator=

移动任务对象 
(公开成员函数)

valid

检查任务对象是否拥有合法函数 
(公开成员函数)

swap

交换二个任务对象 
(公开成员函数)

获取结果

get_future

返回与承诺的结果关联的 std::future 
(公开成员函数)

执行

operator()

执行函数 
(公开成员函数)

make_ready_at_thread_exit

执行函数,并确保结果仅在一旦当前线程退出时就绪
(公开成员函数)

reset

重置状态,抛弃任何先前执行的存储结果 
(公开成员函数)

非成员函数

std::swap(std::packaged_task)

(C++11)

特化 std::swap 算法 
(函数模板)

辅助类

std::uses_allocator<std::packaged_task>

(C++11)(C++17 前)

特化 std::uses_allocator 类型特征  
(类模板特化)

例子:

#include <iostream>#include <future>#include <thread>int main(){// 将一个返回值为7的 lambda 表达式封装到 task 中// std::packaged_task 的模板参数为要封装函数的类型std::packaged_task<int()> task([](){return 7;});// 获得 task 的 futurestd::future<int> result = task.get_future(); // 在一个线程中执行 taskstd::thread(std::move(task)).detach(); std::cout << "Waiting...";result.wait();// 输出执行结果std::cout << "Done!" << std:: endl << "Result is " << result.get() << '\n';}

例子2:

#include <iostream>#include <cmath>#include <thread>#include <future>#include <functional> // 避免对 std::pow 重载集消歧义的独有函数int f(int x, int y) { return std::pow(x,y); } void task_lambda(){    std::packaged_task<int(int,int)> task([](int a, int b) {        return std::pow(a, b);     });    std::future<int> result = task.get_future();     task(2, 9);     std::cout << "task_lambda:\t" << result.get() << '\n';} void task_bind(){    std::packaged_task<int()> task(std::bind(f, 2, 11));    std::future<int> result = task.get_future();     task();     std::cout << "task_bind:\t" << result.get() << '\n';} void task_thread(){    std::packaged_task<int(int,int)> task(f);    std::future<int> result = task.get_future();     std::thread task_td(std::move(task), 2, 10);    task_td.join();     std::cout << "task_thread:\t" << result.get() << '\n';} int main(){    task_lambda();    task_bind();    task_thread();}

输出:

task_lambda: 512task_bind:   2048task_thread: 1024

std::future和std::promise和std::packaged_task相关推荐

  1. C++并发编程线程间共享数据std::future和sd::promise

    线程间共享数据 使用互斥锁实现线程间共享数据 为了避免死锁可以考虑std::lock()或者boost::shared_mutex 要尽量保护更少的数据 同步并发操作 C++标准库提供了一些工具 可以 ...

  2. C++多线程std::async、std::future、std::packaged_task、std::promise

    std::async std::async用于创建异步任务,实际上就是创建一个线程执行相应任务,默认立即开始执行. std::async就是异步编程的高级封装,std::async的操作,其实相当于封 ...

  3. C++ 并发指南<future>(2)std::packaged_task

    std::packaged_task 包装一个可调用的对象,并且允许异步获取该可调用对象产生的结果,从包装可调用对象意义上来讲,std::packaged_task 与 std::function 类 ...

  4. C++ 的 std::promise 和 std::future

    介绍 编写多线程应用时,无法避免的要进行线程间数据同步/通信.std::promise 和 std::future 是 C++ 进行单向数据传递的一种方式.std::promise 是数据的输入端,s ...

  5. C++11中std::future的使用

    C++11中的std::future是一个模板类.std::future提供了一种用于访问异步操作结果的机制.std::future所引用的共享状态不能与任何其它异步返回的对象共享(与std::sha ...

  6. C++11 并发指南四(future 详解三 std::future std::shared_future)

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

  7. C++ 并发指南< future >(3)std::future std::shared_future

    std::future介绍 简单地说,std::future 可以用来获取异步任务的结果,因此可以把它当成一种简单的线程间同步的手段. std::future 通常由某个 Provider 创建,你可 ...

  8. C++11之std::future对象使用说明

    std::future介绍 在前面几篇文章中基本都用到thread对象,它是C++11中提供异步创建多线程的工具.但是我们想要从线程中返回异步任务结果,一般需要依靠全局变量:从安全角度看,有些不妥:为 ...

  9. C++ 多线程:std::future

    概念 我们前面介绍的std::thread 是C++11中提供异步创建多线程的工具,只能是异步运行任务,却无法获取任务执行的结果,一般都是依靠全局对象,全局对象在多线程下是及其不安全的,为此标准库提供 ...

最新文章

  1. FreeSWITCH配置文件详解
  2. 学习Java需要用到什么软件?
  3. JDBC中的SPI实现
  4. 使用不可变对象创建值对象
  5. 如何学习开源项目及Ceph的浅析
  6. 解决:Do not use built-in or reserved HTML elements as component id: form
  7. 谷歌浏览器如何设置flash访问权限
  8. 泰斯花粉阻隔剂 怎么使用
  9. Visual Studio 添加外部工具
  10. 大数据项目开发进度(实时更新)
  11. 一天一个产品分析之美拍_米米米米粒口红_新浪博客
  12. linux mysql backdoor_Mysql BackDoor
  13. 【学习笔记】统计学入门(6/7)——参数估计与可信区间
  14. list容量限制测试
  15. 通过nodejs 服务器读取HTML文件渲染到页面
  16. 螺栓检测项目 螺栓检测报告河南
  17. linux下ImageMagick convert命令
  18. 如何查询搜狗收录情况
  19. PVE虚拟机篇-pve软件换源
  20. ACM-ICPC之路

热门文章

  1. Android中的APinner2
  2. highcharts总结
  3. Android_动态壁纸介绍
  4. 【数据结构与算法】之深入解析“路径总和III”的求解思路与算法示例
  5. 《操作系统》库函数与系统调用的区别和联系?
  6. 智慧显示:5G时代的新机遇
  7. ZYAR20A 亚克力2驱 蓝牙 298寻迹避障机器人 —— 小车前进实验调试
  8. DSP F28335的SCI模块
  9. Orangepi Zero播放声音报错的解决方案
  10. 【Linux网络编程】TCP网络编程中connect listen和accept三者之间的关系