【C++多线程】std::future、std::async、std::promise、std::packaged_task、std::shared_future

2021-04-26 10:29

阅读:413

标签:ast   视图   tail   names   call   代码   自动   lis   start   

  如图以下是头文件中的类容。

技术图片

std::future

  future有两个类模板,一个独占的std::future,也就是只能被获取一次,另一个是共享的std::shared_future。std::future是一个类模板,其中T是要存储的值的类型,std::future 的实例只能与一个指定事件相关联std::future对象在内部存储一个将来会被赋值的值,并提供了一个访问该值的机制,通过get()成员函数实现但如果有人视图在get()函数可用之前通过它来访问相关的值,那么get()函数将会阻塞,直到该值可用。std::future的一个对象,可以从某个对象(std::promise和std::packaged_task)或函数(std::async())获取值,并在不同线程之间提供恰当的同步访问。如std::async 会返回一个 std::future 对象,这个对象持有最终计算出来的结果。当你需要这个值时,你只需要调用这个对象的get()成员函数,他会阻塞到当前位置,知道获取到那个返回的值。如前所述,std::future通常与std::async()函数和std::promise、std::packaged_tast对象一起使用。

  • std::future对象的get()成员函数会等待线程执行结束并返回结果,拿不到结果它就会一直等待,感觉有点像join()但是,它是可以获取结果的。
  • std::future对象的wait()成员函数,用于等待线程返回,本身并不返回结果,这个效果和 std::thread 的join()更像。
  • std::future对象的share()成员函数,将该future对象返回为shared_future的对象。

技术图片

 1 // future example
 2 #include // std::cout
 3 #include // std::async, std::future
 4 #include // std::chrono::milliseconds
 5 
 6 // a non-optimized way of checking for prime numbers:
 7 bool is_prime (int x) {
 8   for (int i=2; iif (x%i==0) return false;
 9   return true;
10 }
11 
12 int main ()
13 {
14   // call function asynchronously:
15   std::futurebool> fut = std::async (is_prime,444444443); 
16 
17   // do something while waiting for function to set future:
18   std::cout "checking, please wait";
19   std::chrono::milliseconds span (100);
20   while (fut.wait_for(span)==std::future_status::timeout)
21     std::cout ‘.  std::flush;
22 
23   bool x = fut.get();     // retrieve return value
24 
25   std::cout "\n444444443 " "is":"is not") " prime.\n";
26 
27   return 0;
28 }

  std::future_status是枚举类型,表示异步任务的执行状态。std::future和std::shared_future的成员函数wait_for()和wait_until()会返回该类型。类型的取值有

  • std::future_status::ready

  • std::future_status::timeout

  • std::future_status::deferred

std::async()

  std::async()是一个函数模板,用来启动一个异步任务,启动起来一个异步任务之后,它返回一个std::future对象。std::async()使用方法和std::thread()相同

  *std::thread产生的线程需要在主线程中调用需要join或者detach,否则会出现异常,而std::async产生的线程不需要我们做任何处理。

 1 #include  2 #include  3 using namespace std;
 4 class A {
 5 public:
 6     int mythread(int mypar) {
 7         cout  endl;
 8         return mypar;
 9     }
10 };
11  
12  
13 int mythread() {
14     cout "mythread() start" "threadid = "  endl;
15     std::chrono::milliseconds dura(5000);
16     std::this_thread::sleep_for(dura);
17     cout "mythread() end" "threadid = "  endl;
18     return 5;
19 }
20  
21  
22 int main() {
23     A a;
24     int tmp = 12;
25     cout "main" "threadid = "  endl;
26     std::futureint> result1 = std::async(mythread);
27     cout "continue........"  endl;
28     cout get() //阻塞在这里等待mythread()执行完毕,拿到结果
29     
30     //类成员函数
31     std::futureint> result2 = std::async(&A::mythread, &a, tmp); //第二个参数是对象引用才能保证线程里执行的是同一个对象
32     cout get()  endl;
33    //或者result2.wait();
34     cout "good luck"  endl;
35     return 0;
36 }

  我们通过向std::async()传递一个参数,改参数是std::launch类型(枚举类型),来达到一些特殊的目的:

  1、std::lunch::deferred(defer推迟,延期)表示线程入口函数的调用会被延迟,一直到std::future的wait()或者get()函数被调用时(由主线程调用)才会执行;如果wait()或者get()没有被调用,则不会执行。

实际上根本就没有创建新线程。std::lunch::deferred意思时延迟调用,并没有创建新线程,是在主线程中调用的线程入口函数。

  2、std::launch::async,在调用async函数的时候就开始创建新线程。就是std::async()的默认情况。

 1 #include  2 #include  3 using namespace std;
 4  
 5 int mythread() {
 6     cout "mythread() start" "threadid = "  endl;
 7     std::chrono::milliseconds dura(5000);
 8     std::this_thread::sleep_for(dura);
 9     cout "mythread() end" "threadid = "  endl;
10     return 5;
11 }
12  
13  
14 int main() {
15     cout "main" "threadid = "  endl;
16     std::futureint> result1 = std::async(std::launch::deferred ,mythread);
17     cout "continue........"  endl;
18     cout get() //卡在这里等待mythread()执行完毕,拿到结果
19     cout "good luck"  endl;
20     return 0;
21 }

技术图片

std::promise 

  还有让std::future 与一个任务实例相关联的唯一方式,可以将任务包装入一个 std::packaged_task 实例中,或使用 std::promise 类型模板显示设置值。与 std::promise 对比, std::packaged_task 具有更高层的抽象。

  std::promise也是一个类模板,其对象有可能在将来对值进行赋值,每个std::promise对象有一个对应的std::future对象, std::promise保存的值可被与之关联的std::future读取,读取操作可以发生在其它线程std::promise允许move语义(右值构造,右值赋值),但不允许拷贝(拷贝构造、赋值),std::future亦然。std::promise是合法的,此时std::promise.set_value不接受任何参数,仅用于通知关联的std::future.get()解除阻塞。

技术图片

   std::promise和std::future合作共同实现了多线程间通信。

 1 #include  2 #include  3 #include  4 #include  5  
 6 // 线程B
 7 void initiazer(std::promiseint> * promObj)
 8 {
 9     std::cout "Thread B"  std::endl;
10     // set the value at proper time
11     std::this_thread::sleep_for(std::chrono::seconds(3));
12     promObj->set_value(23);
13 }
14  
15 int main()
16 {
17         // 线程A
18     std::promiseint> promiseObj;
19     std::futureint> futureObj = promiseObj.get_future();
20     
21     std::thread th(initiazer, &promiseObj); // 启动线程B
22     
23     // 获取对象的值,该调用在B设置其值后会返回23,在B设置其值前会阻塞
24     std::coutget()  std::endl;
25     
26     th.join();
27     
28     return 0;
29 }
30 
31 //输出23

 

 1 #include // std::cout, std::endl
 2 #include // std::thread
 3 #include string>   // std::string
 4 #include // std::promise, std::future
 5 #include // seconds
 6 using namespace std::chrono;
 7 //线程B
 8 void read(std::future<:>string> *future) {
 9     // future会一直阻塞,直到有值到来
10     std::cout get()  std::endl;
11 }
12 //线程A
13 int main() {
14     // promise 相当于生产者
15     std::promise<:>string> promise;
16     // future 相当于消费者, 右值构造
17     std::future<:>string> future = promise.get_future();
18     // 另一线程中通过future来读取promise的值
19     std::thread thread(read, &future);
20     // 让read等一会儿:)
21     std::this_thread::sleep_for(seconds(1));
22     // 
23     promise.set_value("hello future");
24     // 等待线程执行完成
25     thread.join();
26 
27     return 0;
28 }
29 // 控制台输: hello future

  如上代码中,一旦std::promise对象调用set_value设置了对象的值,该对象的共享状态就变更为ready,std::future对象就能使用get()函数获取到值。

注意:

  • 只能从promise共享状态获取一个future对象,不能把两个future关联到同一个promise
  • 如果promise不设置值或者异常,promise 对象在析构时会自动地设置一个 future_error 异常(broken_promise)来设置其自身的就绪状态
  • promise 对象的set_value只能被调用一次,多次调用会抛出std::future_error异常(因为第一次调用后状态变更为ready)
  • std::future是通过std::promise::get_future获取到的,自己构造出来的无效

 

  如果promise直到销毁时,都未设置过任何值,则promise会在析构时自动设置为std::future_error,这会造成std::future.get抛出std::future_error异常。
 1 #include // std::cout, std::endl
 2 #include // std::thread
 3 #include // std::promise, std::future
 4 #include // seconds
 5 using namespace std::chrono;
 6 
 7 void read(std::futureint> future) {
 8     try {
 9         future.get();
10     } catch(std::future_error &e) {
11         std::cerr "\n"  std::endl;
12     }
13 }
14 
15 int main() {
16     std::thread thread;
17     {
18         // 如果promise不设置任何值
19         // 则在promise析构时会自动设置为future_error
20         // 这会造成future.get抛出该异常
21         std::promiseint> promise;
22         thread = std::thread(read, promise.get_future());
23     }
24     thread.join();
25 
26     return 0;
27 }

  通过std::promise::set_exception函数可以设置自定义异常,该异常最终会被传递到std::future,并在其get函数中被抛出。

 1 #include  2 #include  3 #include  4 #include // std::make_exception_ptr
 5 #include // std::logic_error
 6 
 7 void catch_error(std::futurevoid> &future) {
 8     try {
 9         future.get();
10     } catch (std::logic_error &e) {
11         std::cerr "logic_error: "  std::endl;
12     }
13 }
14 
15 int main() {
16     std::promisevoid> promise;
17     std::futurevoid> future = promise.get_future();
18 
19     std::thread thread(catch_error, std::ref(future));
20     // 自定义异常需要使用make_exception_ptr转换一下
21     promise.set_exception(
22         std::make_exception_ptr(std::logic_error("caught")));
23     
24     thread.join();
25     return 0;
26 }
27 // 输出:logic_error: caught

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 的共享状态的生命周期一直持续到最后一个与之相关联的对象被释放或者销毁为止。

技术图片

 

 

 

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

 

std::shared_future

 std::shared_future:也是个类模板,可以让多个线程等待同一个事件,用法和std::future差不多。区别是std::future的 get() 成员函数是转移数据,只能get()一次; std::shared_future 的 get()成员函数是复制数据,可以get()多次。

技术图片

 

 

   获取多次

 1 #include  2 #include  3 #include  4 using namespace std;
 5  
 6 int mythread() {
 7     cout "mythread() start" "threadid = "  endl;
 8     std::chrono::milliseconds dura(5000);
 9     std::this_thread::sleep_for(dura);
10     cout "mythread() end" "threadid = "  endl;
11     return 5;
12 }
13 
14 int main() {
15     cout "main" "threadid = "  endl;
16     std::packaged_taskint()> mypt(mythread);
17     std::thread t1(std::ref(mypt));
18     std::futureint> result = mypt.get_future();
19     
20     bool ifcanget = result.valid(); //判断future 中的值是不是一个有效值
21     std::shared_futureint> result_s(result.share()); //执行完毕后result_s里有值,而result里空了
22     //std::shared_future result_s(std::move(result));
23    //通过get_future返回值直接构造一个shared_future对象
24    //std::shared_future result_s(mypt.get_future());
25    t1.join();
26     
27     auto myresult1 = result_s.get();
28     auto myresult2 = result_s.get();
29  
30     cout "good luck"  endl;
31     return 0;
32 }

  在每一个 std::shared_future 的独立对象上成员函数调用返回的结果还是不同步的,所以为了在多个线程访问一个独立对象时,避免数据竞争,必须使用锁来对访问进行保护。优先使用的办法:为了替代只有一个拷贝对象的情况,可以让每个线程都拥有自己对应的拷贝对象。这样,当每个线程都通过自己拥有的 std::shared_future 对象获取结果,那么多个线程访问共享同步结果就是安全的。

技术图片

参考

http://www.cplusplus.com/reference/future/

https://blog.csdn.net/qq_38231713/article/details/106092879 

https://www.jianshu.com/p/7945428c220e

 

【C++多线程】std::future、std::async、std::promise、std::packaged_task、std::shared_future

标签:ast   视图   tail   names   call   代码   自动   lis   start   

原文地址:https://www.cnblogs.com/chen-cs/p/13252591.html


评论


亲,登录后才可以留言!