以下内容整理自:https://www.cnblogs.com/my_life/articles/5401190.html

future 是一个能从其他地方获取到一个值的对象,如果是在不同的线程中,则被synchronizing properly.

std::condition_variable 可以用于异步事件的重复通知,但是有些时候可能只等待事件发生一次,比如:等待特定的航班,用条件变量大杀器有点浪费了。C++11 标准库提供了几种异步任务机制。通常 thread 不能返回线程执行的结果(可以通过引用参数返回),而在异步处理当中很多时候都需要获得计算的结果。如果只获取结果一次那么选用 future,即通过 future 获取了结果后,后续再通过此 future 获取结果将会出错。

future,async,packaged_task,promise 用法简介:

  std::future可用于异步任务中获取任务结果,但是它只是获取结果而已,真正的异步调用需要配合 std::async, std::promise, std::packaged_task。这里 async 是个模板函数,promise 和 packaged_task 是模板类,通常模板实例化参数是任务函数(callable object)。下面是它们的部分组合用法:假设计算任务 int task(string x);

1. async + future简单用法:

代码:

 // future::operator=
#include <iostream> // std::cout
#include <future> // std::async, std::future int get_value() { return ; } int main ()
{
std::future<int> fut; // default-constructed, 默认构造的 future 对象是无效的 // async()返回的就是一个 uture 对象。 fut = future_returned_by_async(), 调用 future 的赋值运算符。
// move-assigned,赋值运算符隐式使用的是 move 语意(非c++98的拷贝语意), fut = 这使得 fut 变的有效。同时使得右操作数变的无效
fut = std::async (get_value);
//async创建并运行一个线程,返回一个与函数返回值相对应类型的 future,通过它我们可以在其他任何地方获取异步结果 //Calling future::get on a valid future blocks the thread until the provider makes the shared state ready return 0;
std::cout << "value: " << fut.get() << '\n';
}

注意:futrue 的使用形式:future<int> myFuture=async(task,10)

//函数立即返回,不会等 task 执行完毕。async 的效果是:自动创建一个后台线程(可以选取一个空闲的线程), 将来会在某一时刻执行任务 task 函数,并将计算结果保存在 myFuture 中,这里 future 的模板参数要和任务 task 返回类型一致为 int。怎样获得任务结果呢?通常原来的线程(即创建 myFuture 的线程记为 A,不是 async 执行 task 那个线程)可以执行其它操作,直到其想要获取 task 的结果时调用 int x= myFuture.get() 即可获得 task 的执行结果并保存至 x 中。注意若 task 没有执行完就调用了myFuture.get() 那么线程A将会阻塞直到task完成

2.packaged_task + future简单用法:

packaged_task 用来包裹一个可调用的对象(包括函数,函数指针,函数对象/仿函数,成员函数等)

packaged_task 对象包含两个对象:

  1. stored task, which is some callable object (such as a function pointer, pointer to member or function object) whose call signature shall take arguments of the types in Args... and return a value of type Ret.
  2. shared state, which is able to store the results of calling the stored task (of type Ret) and be accessed asynchronously through a future.

packaged_task 本身不会创建新线程:

 packaged_task<int(int)> myPackaged(task);
//首先创建packaged_task对象myPackaged,其内部创建一个函数task和一个共享状态(用于返回task的结果)
future<int> myFuture = myPackaged.get_future();
//通过 packaged_task::get_future() 返回一个future对象myFuture用于获取task的任务结果
thread myThread(move(myPackaged),"hello world");
//创建一个线程执行task任务,这里注意move语义强制将左值转为右值使用因为packaged_task禁止copy constructor,可以不创建线程,那么task任务的执行将和future结果的获取在同一个线程,这样就不叫异步了
//这里主线程可以做其它的操作
int x = myFuture.get();
//线程还可以在执行一些其它操作,直到其想获取task的结果时调用此语句

3.promise + future 简单用法,摘自 cplusplus 的范例:

 #include <iostream>       // std::cout
#include <functional> // std::ref
#include <thread> // std::thread
#include <future> // std::promise, std::future void print_int (std::future<int>& fut) {
int x = fut.get();//当promise::set_value()设置了promise的共享状态值后,
// fut将会通过future::get()获得该共享状态值,若promise没有设置该值那么
// fut.get()将会阻塞线程直到共享状态值被promise设置
std::cout << "value: " << x << '\n';//输出:<span style="font-family: monospace; white-space: pre; rgb(231, 231, 231);">value: 10</span>
} int main ()
{
std::promise<int> prom; //创建一个promise对象
std::future<int> fut = prom.get_future(); //获取promise内部的future,fut将和promise共享promise中的共享状态,
// 该共享状态用于返回计算结果
std::thread th1 (print_int, std::ref(fut)); //创建一个线程,并通过引用方式将fut传到print_int中
prom.set_value (); //设置共享状态值
//
th1.join();//等待子线程
return ;
}

将主线程即需要 task 结果的线程称为 provider,称执行任务 task 或上面 print_int 的线程为 executor (这里只是为了后面表述方便,没有科学考证的)。从上面的例子可以看出,简单的同步机制都是通过设置某种共享状态然后通过 future 获取该共享状态达到同步

  async 通过创建或者选取一个当前空闲线程执行任务,然后将计算结果保存至与此 async 相关的 future 中,期间只有存取结果值,没有其它的交互,并且是 provider 持有future,executor 执行任务

  packaged_task 是一个对象其内部持有 callable object,provider 创建一个下线程 executor 执行任务,最后 provider 通过相关的 future 获取任务计算结果。和 async 差不多。只有任务结果的存取,没有其它交互

  promise 是 provider 持有,executor 持有相关的 future,然后 provider 通过 promise 设定共享状态的值,future 获取该共享值后执行某些任务。形式上和前面两个有点相反

细看 future,async,packaged_task,promise:

future可以获取计算的结果,用于不同线程间的简单同步,future 的创建方式:async, packaged_task::get_future ,  promise::get_future 这三种返回有效的future,这里有效是指future和某个共享状态关联:

 future() noexcept;//创建一个空的future,其不和任何共享状态相关,注意该future是invalid的,但是其可以move
future (const future&) = delete;//禁止拷贝构造
future (future&& x) noexcept;//具有move语义
~future();//解除和某个共享状态的关联,若该future是最后一个和共享状态关联的则共享状态也被销毁,
// 因为future是禁止拷贝的,所以这里最后一个可以理解为该future是valid的(和某个共享状态关联)
future& operator= (future&& rhs) noexcept;//移动赋值,若rhs是valid的那么赋值后rhs将不再和该共享状态关联,
// 赋值后的future和该共享状态关联
future& operator= (const future&) = delete;//禁止拷贝赋值
shared_future<T> share();//返回一个shared_future,shared_future允许多个shared_future和共
// 享状态关联并且可以多次get,而future只允许一个future和共享状态关联。调用share()的future不再
// 和共享状态关联,如future<int> f=async(task); shared_future<int> sh=f.share(); f.get(); f.get()*2;
bool valid() const noexcept;//若future和共享状态关联则返回true,否则返回false
T get();//若future和某个T类型的共享状态关联,那么调用future::get(),
// 若该状态还没有准备好阻塞线程直到准备好,若状态准备好了,则返回该状态值,
// 并将future和共享状态解绑,此future将invalid
void wait() const;//阻塞等待共享状态就绪
future_status wait_for (const chrono::duration<Rep,Period>& rel_time) const;
//在rel_time时间内等待共享状态值就绪
future_status wait_until (const chrono::time_point<Clock,Duration>& abs_time) const;
//直到abs_time时刻等待共享状态就绪

future_status:

返回值 描述
future_status::ready 共享状态的标志已经变为 ready,即 Provider 在共享状态上设置了值或者异常。
future_status::timeout 超时,即在规定的时间内共享状态的标志没有变为 ready。
future_status::deferred 共享状态包含一个 deferred 函数。

async 开启后台线程执行任务:

 async (Fn&& fn, Args&&... args);
//自动选择线程执行任务fn,args是fn的参数,若fn是某个对象的非静态成员函数那么第
// 一个args必须是对象的名字,后面的args是fn所需的参数
async (launch policy, Fn&& fn, Args&&... args);//有三种方式policy执行任务fn
policy=launch::async表示开启一个新的线程执行fn
policy=launch::deferred 表示fn推迟到future::wait/get时才执行
policy=launch::async|launch::deferred表示由库自动选择哪种机制执行fn,和第一种构造方式async(fn,args)策略相同

packaged_task 类似于 std::function 但是其允许异步存取结果,其内部持有一个函数调用和共享状态,该共享状态可以被 packaged_task 返回的 future 获取:

 packaged_task() noexcept;//空的packaged_task对象,没有共享状态和内部函数
explicit packaged_task (Fn&& fn);//内部有共享状态和函数fn
explicit packaged_task (allocator_arg_t aa, const Alloc& alloc, Fn&& fn);
//共享状态通过alloc分配内存(该共享状态可能是个buffer)
packaged_task (const packaged_task&) = delete;//禁止拷贝构造
packaged_task (packaged_task&& x) noexcept;//具有移动语义 ~packaged_task();//丢弃共享状态,若还有future和共享状态关联,
// 那么共享状态不会被销毁直到future销毁,如果析构发生时共享
// 状态还没有被设置那么析构将设置共享状态并在状态里加入异常 packaged_task& operator= (packaged_task&& rhs) noexcept;//rhs的共享状态将被移动,rhs将没有共享状态
ackaged_task& operator= (const packaged_task&) = delete;//禁止拷贝 bool valid() const noexcept;//若packaged_task内部有共享状态则返回true,否则返回false
future<Ret> get_future();//返回一个future用以获得共享状态,该函数只能被调用一次 void operator()(Args... args);//执行fn,若成功则将结果写入共享状态,
// 若失败则写入异常到共享状态,通过future::get()可以获取该状态
void make_ready_at_thread_exit (args... args);//结果介入共享状态,
// 但是在该函数所在的调用线程结束后才使共享状态就绪,即该线程结束
// 后future::get()才能获取状态值,若在写入状态值和线程没有退出期间有写入该状态的行为将抛出future_error的异常 void swap (packaged_task& x) noexcept;//交换两个packaged_task的内部share state 和 callable object

 promise可以存入一个共享状态值,相关的std::future可以获取该值:

 promise();//空的promise对象,没有共享状态值
template <class Alloc> promise (allocator_arg_t aa, const Alloc& alloc);//Alloc将为共享状态值开辟内存
promise (const promise&) = delete;//禁止拷贝赋值
romise (promise&& x) noexcept;//具备移动语义
~promise();//和~packaged_task()语义一样 promise& operator= (promise&& rhs) noexcept;//移动赋值,rhs不再有共享状态
promise& operator= (const promise&) = delete; future<T> get_future();//返回一个future和共享状态关联,可以通过此future获取共享状态的值或异常,该函数只能被调用一次 void set_value (const T& val);//设置共享状态的值
void set_value (const T& val);
void promise<R&>::set_value (R& val); // when T is a reference type (R&)
void promise<void>::set_value (void); // when T is void void set_exception (exception_ptr p);//设置异常指针p到共享状态中,若状态关联的future::get()会获得该异常(并解除阻塞) void set_value_at_thread_exit (const T& val);//和packaged_task::make_ready_at_thread_exit()语义一样
void set_value_at_thread_exit (T&& val);
void promise<R&>::set_value_at_thread_exit (R& val); // when T is a reference type (R&)
void promise<void>::set_value_at_thread_exit (void); // when T is void void set_exception_at_thread_exit (exception_ptr p);//设置异常到共享状态中,但是在线程结束时才使共享状态就绪 void swap (promise& x) noexcept;//交换两个promise对象的共享状态

shared_future 和 future 的区别是:一个 future 对象和一个共享状态相关,且转移只能通过 move 语义。但是多个 shared_future 对象可以和共享状态相关(即多对一)。std::shared_future 与 std::future 类似,但是 std::shared_future 可以拷贝、多个 std::shared_future 可以共享某个共享状态的最终结果(即共享状态的某个值或者异常)。shared_future 可以通过某个 std::future 对象隐式转换(参见 std::shared_future 的构造函数),或者通过 std::future::share() 显示转换,无论哪种转换,被转换的那个 std::future 对象都会变为 not-valid:

 shared_future() noexcept;
shared_future (const shared_future& x);
shared_future (shared_future&& x) noexcept;
shared_future (future<T>&& x) noexcept;
下面的成员函数和future差不多:
operator=
赋值操作符,与 std::future 的赋值操作不同,std::shared_future 除了支持 move 赋值操作外,还支持普通的赋值操作。
get
获取与该 std::shared_future 对象相关联的共享状态的值(或者异常)。
valid
有效性检查。
wait
等待与该 std::shared_future 对象相关联的共享状态的标志变为 ready。
wait_for
等待与该 std::shared_future 对象相关联的共享状态的标志变为 ready。(等待一段时间,超过该时间段wait_for 返回。)
wait_until
等待与该 std::shared_future 对象相关联的共享状态的标志变为 ready。(在某一时刻前等待,超过该时刻 wait_until 返回。)

通常线程池采用模板实现时各线程执行的都是相同类型的任务,若采用 packaged_task 可以将不同类型的函数对象封转在其内部,每个线程取走一个 packaged_task 执行,那么线程池执行的任务可以不同

下面是一个GUI中一个线程专门接收用户任务并压入任务队列,另一个线程专门执行用户任务:

 std::mutex m;
std::deque<std::packaged_task<void()> > tasks;
bool gui_shutdown_message_received();
void get_and_process_gui_message();
void gui_thread()
{
while(!gui_shutdown_message_received())//不断获取用户任务
{
get_and_process_gui_message();
std::packaged_task<void()> task;
{
std::lock_guard<std::mutex> lk(m);
if(tasks.empty())
continue;
task=std::move(tasks.front());//
tasks.pop_front();
}
task();
}
}
std::thread gui_bg_thread(gui_thread);
template<typename Func>
std::future<void> post_task_for_gui_thread(Func f)//添加任务
{
std::packaged_task<void()> task(f);
std::future<void> res=task.get_future();
std::lock_guard<std::mutex> lk(m);
tasks.push_back(std::move(task));//
return res;
}

c++多线程基础5(future,async,packaged_task,promise)的更多相关文章

  1. C++并发与多线程学习笔记--async、future、packaged_task、promise

    async future packaged_task promise async std:async 是个函数,用来启动一个异步任务,启动起来一个异步任务之后,返回一个std::futre对象,启动一 ...

  2. 多线程07:async、future、packaged_task、promise

    async.future.packaged_task.promise 本节内容需要包含头文件:#include <future> 一.std::async. std::future 创建后 ...

  3. C++11并发编程:async,future,packaged_task,promise

    一:async std::async:用于创建异步任务,可以代替创建线程,函数原型:async(std::launch::async | std::launch::deferred, f, args. ...

  4. C++11之std::future和std::promise和std::std::packaged_task

    为什么C++11引入std::future和std::promise?C++11创建了线程以后,我们不能直接从thread.join()得到结果,必须定义一个变量,在线程执行时,对这个变量赋值,然后执 ...

  5. 【C++并发实战】(三) std::future和std::promise

    std::future和std::promise std::future std::future期待一个返回,从一个异步调用的角度来说,future更像是执行函数的返回值,C++标准库使用std::f ...

  6. C++11之std::future和std::promise

    为什么C++11引入std::future和std::promise?C++11创建了线程以后,我们不能直接从thread.join()得到结果,必须定义一个变量,在线程执行时,对这个变量赋值,然后执 ...

  7. C++多线程基础教程

    目录 1 什么是C++多线程? 2 C++多线程基础知识 2.1 创建线程 2.2 互斥量使用 lock()与unlock(): lock_guard(): unique_lock: conditio ...

  8. Java多线程干货系列—(一)Java多线程基础

    前言 多线程并发编程是Java编程中重要的一块内容,也是面试重点覆盖区域,所以学好多线程并发编程对我们来说极其重要,下面跟我一起开启本次的学习之旅吧. 正文 线程与进程 1 线程:进程中负责程序执行的 ...

  9. swift开发多线程篇 - 多线程基础

    swift开发多线程篇 - 多线程基础 iOS 的三种多线程技术 (1)NSThread  使用NSThread对象建立一个线程非常方便 但是!要使用NSThread管理多个线程非常困难,不推荐使用 ...

随机推荐

  1. IntelliJ IDEA 安装 Julia 插件

    打开 IntelliJ IDEA 点击 Configure 选择 Plugins 然后点击 Browse repositories 搜索 Julia,然后点击 Install 进行安装 安装完重启一下 ...

  2. pandas层级索引1

    层级索引(hierarchical indexing) 下面创建一个Series, 在输入索引Index时,输入了由两个子list组成的list,第一个子list是外层索引,第二个list是内层索引. ...

  3. .net 连接ORACLE中文显示乱码解决方案

    FYI由于历史的原因,早期的oracle没有中文字符集(如oracle6.oracle7.oracle7.1),但有的用户从那时起就使用数据库了, 并用US7ASCII字符集存储了中文,或是有的用户在 ...

  4. 最长上升子序列(LIS)

    最长递增子序列,Longest Increasing Subsequence 下面我们简记为 LIS.排序+LCS算法 以及 DP算法就忽略了,这两个太容易理解了. 假设存在一个序列d[1..9] = ...

  5. 01.webservice介绍

    不实现异构,实现远程系统之间的调用.

  6. std::mutex与pthread mutex区别

    Linux下 pthread mutex * PTHREAD_MUTEX_TIMED_NP,这是缺省值,也就是普通锁.当一个线程加锁以后,其余请求锁的线程将形成一个等待队列,并在解锁后按优先级获得锁. ...

  7. Luogu 5043 【模板】树同构([BJOI2015]树的同构)

    BZOJ 4337 简单记录一种树哈希的方法:以$x$为根的子树的哈希值为$\sum_{y \in son(x)}f_y*base_i$,$f_y$表示以$y$为根的树的哈希值,其中$i$表示$f_y ...

  8. su 和sudo su 的区别

    su "user" 执行该命令,需要输入password,它是"user"中定义的用户的password,即,要变换成的用户的password.(如果已经用ro ...

  9. [redis]redis-cluster的使用

    1.为集群添加一个主节点 首先准备一个全新的redis文件夹,这里我们叫做为7007 [root@CentOS7 redis-cluster]# ls [root@CentOS7 redis-clus ...

  10. 列表推导式对比For循环执行效率

    我们在前面的学习中都知道,如果把1-10以内的元素追加到一个新的列表表中,如果使用for循环我们可以这么做: a = [] for i in range(1,11): a.append(i) prin ...