当前位置:   article > 正文

C++多线程编程(3) 异步操作类 std::future std::promise std::async_std::promise和std::future异步发送

std::promise和std::future异步发送

C++中提供了异步操作相关的类:
1. std::future: 异步结果的传输通道,可以很方便的获取线程函数的返回值。

    在C++中,如果希望获取线程函数的返回值,就不能直接通过thread.join()得到结果,这时就必须定义一个变量,在线程函数中去给这个变量赋值,然后执行join,最后得到结果,这是一个非常繁琐的过程。C++11 的 thread 库提供了future,用来访问异步操作的结果。为什么会被命名为future呢:这是因为一个异步操作的结果不能马上获取,只能在未来某个时候从某个地方获取,这个异步操作的结果是一个未来的期待值,所以被称为future,future相当于提供了一个获取异步操作结果的通道。可以通过同步等待的方式获取结果,也可以通过查询future的状态来获取异步操作的结果:

   future的状态为 future_status , 共有三种状态:

1.Deferred: 异步操作还没有开始

2. Ready: 异步操作已经完成

3. Timeout: 异步操作超时

2.std::promise:

   std::promise将数据和future绑定起来,为获取线程函数中的某个值提供便利,在线程函数中为外面传进来的promise赋值,在线程函数执行完之后,就可以通过pormise的future获取该值了。取值是间接的通过promise内部提供的future进行的。

具体的关系我个人理解为如下的图:

在线程函数的外部创建std::promise,然后将它作为线程函数的参数传入,在线程函数中为其赋值。然后在线程函数外部通过promise 的 get_future()方法创建 std::future,再通过future的get方法获取变量的值即可。所以从图中可以看出,future相当于异步结果的输出通道。而这个通道是位于std::promise内部的。

使用方法如下:

使用future的get方法,获得任务执行的返回值, 但是如果当前任务尚未执行, 任务会触发立即执行, 并且堵塞当前线程,直到任务完成

  1. // ConsoleApplication1.cpp : 定义控制台应用程序的入口点。
  2. #include "stdafx.h"
  3. #include <iostream>
  4. #include <future>
  5. #include <thread>
  6. void task(std::promise<int> &prom , int para) // promise作为函数的参数
  7. {
  8. int res = para * 10;
  9. prom.set_value_at_thread_exit(res); // 将线程中需要输出的值存放到promise中
  10. }
  11. int main()
  12. {
  13. std::promise<int> promise_; // 创建promise
  14. std::thread t1(task, std::ref(promise_), 12); // 将promise作为参数传入到线程函数中
  15. t1.join();
  16. std::future<int> f = promise_.get_future(); // 创建通道 通道输出数据的类型
  17. std::cout << "The task output " << f.get() << std::endl;
  18. return 0;
  19. }

当然,也可以在一个线程中执行线程函数,在拎一个线程中获取线程函数中需要输出的值

  1. // ConsoleApplication1.cpp : 定义控制台应用程序的入口点。
  2. #include "stdafx.h"
  3. #include <iostream>
  4. #include <future>
  5. #include <thread>
  6. void task(std::promise<int> &prom , int para) // promise作为函数的参数
  7. {
  8. int res = para * 10;
  9. prom.set_value_at_thread_exit(res); // 将线程中需要输出的值存放到promise中
  10. }
  11. void get_task_value(std::future<int> &future) // future作为函数的参数
  12. {
  13. std::cout << "The task output " << future.get() << std::endl;
  14. }
  15. int main()
  16. {
  17. std::promise<int> promise_; // 创建promise
  18. std::future<int> future = promise_.get_future(); // 创建通道
  19. std::thread t1(task, std::ref(promise_), 12); // 将promise作为参数传入到线程函数中 线程函数
  20. std::thread t2(get_task_value, std::ref(future)); // 获取线程函数值的线程
  21. t1.join();
  22. t2.join();
  23. return 0;
  24. }

可以通过查询future的状态来获取异步任务的执行情况,例如,可以在上面的代码中添加future的状态查询,直到任务完成为止。

 future的状态为 future_status , 共有三种状态:

1.Deferred: 异步操作还没有开始

2. Ready: 异步操作已经完成

3. Timeout: 异步操作超时

  1. // ConsoleApplication1.cpp : 定义控制台应用程序的入口点。
  2. #include "stdafx.h"
  3. #include <iostream>
  4. #include <future>
  5. #include <thread>
  6. #include <chrono>
  7. void task(std::promise<int> &prom , int para) // promise作为函数的参数
  8. {
  9. std::this_thread::sleep_for(std::chrono::seconds(2)); // 线程延时2s
  10. int res = para * 10;
  11. prom.set_value_at_thread_exit(res); // 将线程中需要输出的值存放到promise中
  12. }
  13. void get_task_value(std::future<int> &future) // future作为函数的参数
  14. {
  15. std::future_status status;
  16. do
  17. {
  18. status = future.wait_for(std::chrono::milliseconds(200)); // 等待时间
  19. if (status == std::future_status::deferred)
  20. {
  21. // 异步操作还没有开始
  22. std::cout << "异步操作还没有开始" << std::endl;
  23. }
  24. else if (status == std::future_status::timeout)
  25. {
  26. // 异步操作超时, 就是还没有完成的意思
  27. std::cout << "异步操作超时" << std::endl;
  28. }
  29. else
  30. {
  31. // 异步操作已经完成
  32. std::cout << "The task output " << future.get() << std::endl;
  33. }
  34. } while (status != std::future_status::ready);
  35. //std::cout << "The task output " << future.get() << std::endl;
  36. }
  37. int main()
  38. {
  39. std::promise<int> promise_; // 创建promise
  40. std::future<int> future = promise_.get_future(); // 创建通道
  41. std::thread t1(task, std::ref(promise_), 12); // 将promise作为参数传入到线程函数中 线程函数
  42. std::thread t2(get_task_value, std::ref(future)); // 获取线程函数值的线程
  43. t1.join();
  44. t2.join();
  45. return 0;
  46. }

运行结果:

关于shared_future:

使用与futrue相似,shared_futrue类型允许使用第二次, 并且使用获得结果与第一次一样,如果有一场,抛出的异常也是一样的futrue共享状态在get调用后就解除,下次调用会发生报错。 但是使用shared_future的时候,get方法可调用多次,但是结果是一样的,例如:

3.std::package_task

std::package_task包装了一个可调用对象的包装类,它将函数和future绑定起来,(std::promise是将数据和future绑定起来),以便异步调用。package_task和promise有点类似,promise保存的是一个共享的状态值,package_task保存的是一个函数。(其实感觉书上的这句话并没有表述清楚)

实际上,std::future提供了一个访问异步操作结果的机制,它和线程是一个级别的,属于低层次对象。在std::future上的高一层是std::package_task和std::promise. 它们内部都有future以便访问异步操作结果。

std::promise包装的是一个异步操作,如果需要获取异步操作的返回值,就用std::package_task

std::promise包装的是一个值,如果需要获取异步操作中的某个值,就可以使用std::promise

  1. // ConsoleApplication1.cpp : 定义控制台应用程序的入口点。
  2. #include "stdafx.h"
  3. #include <iostream>
  4. #include <future>
  5. #include <thread>
  6. #include <chrono>
  7. double sum(double x, double y)
  8. {
  9. return x + y;
  10. }
  11. int main()
  12. {
  13. // 使用package_task获取返回值
  14. std::packaged_task<double(double, double)> task(sum); // <double(double, double)>函数参数
  15. // 获取future
  16. std::future<double> future = task.get_future();
  17. // 需要将任务移动到线程中进行异步操作
  18. std::thread t1(std::move(task), 2.4, 5.1);
  19. t1.join();
  20. std::cout << "The sum is " << future.get() << std::endl;
  21. return 0;
  22. }

future的wait_for方法是超时等待返回结果,而wait方法知识等待异步操作完成,没有返回值,将上面的方法改写为wait_for方法:
例如:

  1. // ConsoleApplication1.cpp : 定义控制台应用程序的入口点。
  2. #include "stdafx.h"
  3. #include <iostream>
  4. #include <future>
  5. #include <thread>
  6. #include <chrono>
  7. int factorial(int n) // 计算阶乘
  8. {
  9. std::this_thread::sleep_for(std::chrono::milliseconds(100));
  10. if (n == 1)
  11. return n;
  12. return n * factorial(n - 1);
  13. }
  14. void get_result(std::future<int> &future) // 获取结果
  15. {
  16. while (future.wait_for(std::chrono::milliseconds(20)) == std::future_status::timeout) /// 等待线程结束
  17. {
  18. std::cout << "..";
  19. }
  20. std::cout << std::endl;
  21. std::cout << "The result is " << future.get() << std::endl;
  22. }
  23. int main()
  24. {
  25. // 使用package_task获取返回值
  26. std::packaged_task<int(int)> task(factorial); // <double(double, double)>函数参数
  27. // 获取future
  28. std::future<int> future = task.get_future();
  29. // 需要将任务移动到线程中进行异步操作
  30. std::thread t1(std::move(task), 7); // 任务处于调用线程中,移动到线程中进行处理
  31. std::thread t2(get_result, std::ref(future)); // get_result不用move
  32. t1.join();
  33. t2.join();
  34. //std::cout << "The sum is " << future.get() << std::endl;
  35. system("pause");
  36. return 0;
  37. }

线程异步操作函数async

std::async可以用来直接创建异步的task,异步任务返回的结果保存在future中,只需要调用future.get()方法就可以获取到返回值。如果不关注异步任务的结果,则可以调用future.wait()方法,等待任务完成。

async的原型是:

std::async(std::launch::async | std::launch::deferred, f, args);

其中:

第一个参数是创建线程的方式:

std::launch::async在调用async时就创建线程。

std::launch::deferred延迟加载方式创建线程,直到调用了future的get或者wait方法时才会创建线程

第二个参数是线程函数

第三个参数是线程函数的参数

基本用法:

  1. // ConsoleApplication1.cpp : 定义控制台应用程序的入口点。
  2. #include "stdafx.h"
  3. #include <iostream>
  4. #include <future>
  5. #include <thread>
  6. #include <chrono>
  7. int factorial(int n) // 计算阶乘
  8. {
  9. std::this_thread::sleep_for(std::chrono::milliseconds(100));
  10. if (n == 1)
  11. return n;
  12. return n * factorial(n - 1);
  13. }
  14. void func()
  15. {
  16. std::this_thread::sleep_for(std::chrono::seconds(1));
  17. std::cout << "This function has no operation" << std::endl;
  18. }
  19. int main()
  20. {
  21. std::future<int> future1 = std::async(std::launch::deferred, factorial, 5); // 线程函数有返回值
  22. std::future<void> future2 = std::async(std::launch::async, func); // 线程函数没有参数
  23. future2.wait(); // 调用wait()
  24. std::cout << "Factorial is " << future1.get() << std::endl; //调用get()
  25. system("pause");
  26. return 0;
  27. }

也可以使用async来创建线程,一般也推荐这样做:

  1. // ConsoleApplication1.cpp : 定义控制台应用程序的入口点。
  2. #include "stdafx.h"
  3. #include <iostream>
  4. #include <future>
  5. #include <thread>
  6. #include <chrono>
  7. void func1()
  8. {
  9. for (int i = 0; i < 1000; i++)
  10. {
  11. std::cout << "Thread Id is " << std::this_thread::get_id() << std::endl;
  12. }
  13. }
  14. int main()
  15. {
  16. std::future<void> future1 = std::async(std::launch::async, func1); // 线程函数没有参数
  17. std::future<void> future2 = std::async(std::launch::async, func1); // 线程函数没有参数
  18. future1.wait();
  19. future2.wait(); // 调用wait()
  20. system("pause");
  21. return 0;
  22. }

最后介绍一下std::shared_future

shared_future的使用与futrue相似,因为futrue共享状态在get调用后就解除,下次调用会发生报错,而shared_futrue类型允许使用第二次, 并且使用获得结果与第一次一样,如果有一场,抛出的异常也是一样的。创建方式如下:
std::shared_futrue f = std::async(task).share();

  1. // ConsoleApplication1.cpp : 定义控制台应用程序的入口点。
  2. #include "stdafx.h"
  3. #include <iostream>
  4. #include <future>
  5. #include <thread>
  6. #include <chrono>
  7. int func1(int n)
  8. {
  9. return n * 10;
  10. }
  11. int main()
  12. {
  13. std::shared_future<int> future = std::async(std::launch::async, func1, 2).share(); // 线程函数没有参数
  14. std::cout << future.get() << std::endl;
  15. std::cout << future.get() << std::endl; // 第二次调用
  16. system("pause");
  17. return 0;
  18. }

应该优先使用async取代线程的创建,它更加方便的实现了异步调用。

 

-------------------------------------------------------分割线------------------------------------------------------------

 

声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/爱喝兽奶帝天荒/article/detail/922400
推荐阅读
  

闽ICP备14008679号