C++并发实战13:std::future、std::async、std::promise、std::packaged_task(一)

2014-11-24 03:30:33 · 作者: · 浏览: 3

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

(1) 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
  
    myFuture=async(task,10)  
  

//这里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
  
    myPackaged(task);//首先创建packaged_task对象myPackaged其内部创建一个函数task和一个共享状态(用于返回task的结果)
future
   
     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 
  
          // std::cout
#include 
   
     // std::ref #include 
    
      // std::thread #include 
     
       // std::promise, std::future void print_int (std::future
      
       & fut) { int x = fut.get();//当promise::set_value()设置了promise的共享状态值后,fut将会通过future::get()获得该共享状态值,若promise没有设置该值那么fut.get()将会阻塞线程直到共享状态值被promise设置 std::cout << "value: " << x << '\n';//输出:value: 10 } int main () { std::promise
       
         prom; //创建一个promise对象 std::future
        
          fut = prom.get_future(); //获取promise内部的future,fut将和promise共享promise中的共享状态,该共享状态用于返回计算结果 std::thread th1 (print_int, std::ref(fut)); //创建一个线程,并通过引用方式将fut传到print_int中 prom.set_value (10); //设置共享状态值 // th1.join();//等待子线程 return 0; }
        
       
      
     
    
   
  
//从这个例子可以看出promise+future和前面几个有所不同。


将主线程即需要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获取该共享值后执行某些任务。形式上和前面两个有点相反。



(2) 细看future,async,packaged_task,promise

2.1 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
  
    share();//返回一个shared_future,shared_future允许多个shared_future和共享状态关联并且可以多次get,而future只允许一个future和共享状态关