c++多线程基础5(future,async,packaged_task,promise)

以下内容整理自: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简单用法:

代码:
c++多线程基础5(future,async,packaged_task,promise)c++多线程基础5(future,async,packaged_task,promise)

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

View Code
注意: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. A 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 inArgs...and return a value of typeRet.
  2. A shared state, which is able to store the results of calling the stored task (of typeRet) and be accessed asynchronously through afuture.

packaged_task 本身不会创建新线程:
c++多线程基础5(future,async,packaged_task,promise)c++多线程基础5(future,async,packaged_task,promise)

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

View Code

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

c++多线程基础5(future,async,packaged_task,promise)c++多线程基础5(future,async,packaged_task,promise)

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

View Code

将主线程即需要 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和某个共享状态关联:
c++多线程基础5(future,async,packaged_task,promise)c++多线程基础5(future,async,packaged_task,promise)

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

View Code

future_status:

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

async 开启后台线程执行任务:
c++多线程基础5(future,async,packaged_task,promise)c++多线程基础5(future,async,packaged_task,promise)

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

View Code

packaged_task 类似于 std::function 但是其允许异步存取结果,其内部持有一个函数调用和共享状态,该共享状态可以被 packaged_task 返回的 future 获取:
c++多线程基础5(future,async,packaged_task,promise)c++多线程基础5(future,async,packaged_task,promise)

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

View Code

promise可以存入一个共享状态值,相关的std::future可以获取该值:
c++多线程基础5(future,async,packaged_task,promise)c++多线程基础5(future,async,packaged_task,promise)

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

View Code

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:
c++多线程基础5(future,async,packaged_task,promise)c++多线程基础5(future,async,packaged_task,promise)

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

View Code

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

下面是一个GUI中一个线程专门接收用户任务并压入任务队列,另一个线程专门执行用户任务:
c++多线程基础5(future,async,packaged_task,promise)c++多线程基础5(future,async,packaged_task,promise)

1 std::mutex m;  
 2 std::deque<std::packaged_task<void()> > tasks;  
 3 bool gui_shutdown_message_received();  
 4 void get_and_process_gui_message();  
 5 void gui_thread()  
 6 {  
 7    while(!gui_shutdown_message_received())//不断获取用户任务  
 8    {  
 9       get_and_process_gui_message();  
10       std::packaged_task<void()> task;  
11       {  
12           std::lock_guard<std::mutex> lk(m);  
13           if(tasks.empty())  
14               continue;  
15           task=std::move(tasks.front());//  
16           tasks.pop_front();  
17       }  
18       task();  
19    }  
20 }  
21 std::thread gui_bg_thread(gui_thread);  
22 template<typename Func>  
23 std::future<void> post_task_for_gui_thread(Func f)//添加任务  
24 {  
25    std::packaged_task<void()> task(f);  
26    std::future<void> res=task.get_future();  
27    std::lock_guard<std::mutex> lk(m);  
28    tasks.push_back(std::move(task));//  
29    return res;  
30 }

View Code

原文链接: https://www.cnblogs.com/geloutingyu/p/8562283.html

欢迎关注

微信关注下方公众号,第一时间获取干货硬货;公众号内回复【pdf】免费获取数百本计算机经典书籍

原创文章受到原创版权保护。转载请注明出处:https://www.ccppcoding.com/archives/270335

非原创文章文中已经注明原地址,如有侵权,联系删除

关注公众号【高性能架构探索】,第一时间获取最新文章

转载文章受原作者版权保护。转载请注明原作者出处!

(0)
上一篇 2023年2月14日 下午9:09
下一篇 2023年2月14日 下午9:09

相关推荐