跳过正文
  1. Posts/

[c++11] std::async std::packaged_task std::promise and std::future notes

·1397 字·3 分钟
目录

把 std::async、std::packaged_task、std::promise 三个放在一起来说,是因为它们都可以返回一个 std::future 对象。简单来说,当某个线程需要等待一个特定的一次性事件(one-off event),它可以用一个 “future” 来表示这个事件。

std::async
#

有的时候可能你需要做一个花费时间比较长的计算,但是计算结果不是立刻需要。这个时候就可以用一个新的线程来做这个计算。这里比较关键的问题是如何将新线程的计算结果传回当前线程,因为 std::thread 并没有提供一个类似的机制。

这个时候就需要 std::async 登场了。

 1
 2#include <future>
 3#include <iostream>
 4int find_the_answer_to_ltuae();
 5void do_other_stuff();
 6int main()
 7{
 8    std::future<int> the_answer=std::async(find_the_answer_to_ltuae);
 9    do_other_stuff();
10    std::cout<<"The answer is "<<the_answer.get()<<std::endl;
11}

当然,也可以像向 std::thread 包装的 thread function 中传参数一样,向 std::async 中传参数,如下:

 1#include <string>
 2#include <future>
 3struct X
 4{
 5  void foo(int,std::string const&);
 6  std::string bar(std::string const&);
 7};
 8X x;
 9auto f1=std::async(&X::foo,&x,42,"hello");  // 调用p->foo(42, "hello"),p是指向x的指针
10auto f2=std::async(&X::bar,x,"goodbye");  // 调用tmpx.bar("goodbye"), tmpx是x的拷贝副本
11struct Y
12{
13  double operator()(double);
14};
15Y y;
16auto f3=std::async(Y(),3.141);  // 调用tmpy(3.141),tmpy通过Y的移动构造函数得到
17auto f4=std::async(std::ref(y),2.718);  // 调用y(2.718)
18X baz(X&);
19std::async(baz,std::ref(x));  // 调用baz(x)
20class move_only
21{
22public:
23  move_only();
24  move_only(move_only&&)
25  move_only(move_only const&) = delete;
26  move_only& operator=(move_only&&);
27  move_only& operator=(move_only const&) = delete;
28
29  void operator()();
30};
31auto f5=std::async(move_only());  // 调用tmp(),tmp是通过std::move(move_only())构造得到

此外,std::async 还有一个可选参数,值为 std::launch::deferred 或 std::launch::async 或 std::launch::deferred|std::launch::async,第三种为默认参数。

std::launch::async 表示该 task 会立即在一个新的 thread 上执行。

std::launch::deferred 表示该 task 不会被立刻执行,而是在调用 get() 或者 wait() 的时候才会执行。这里需要注意的是,调用 get() 或者 wait() 的线程,可以和调用 async() 属于同一个线程,也可以属于不同线程。

笼统来说,std::launch::deferred 是一种更为灵活的方式。具体请参考 cppreference_async

std::packaged_task
#

std::packaged_task 有点类似 std::function,把要在其他线程执行的对象封装了起来。

 1#include <iostream>
 2#include <cmath>
 3#include <thread>
 4#include <future>
 5#include <functional>
 6
 7// unique function to avoid disambiguating the std::pow overload set
 8int f(int x, int y) { return std::pow(x,y); }
 9
10void task_lambda()
11{
12    std::packaged_task<int(int,int)> task([](int a, int b) {
13        return std::pow(a, b);
14    });
15    std::future<int> result = task.get_future();
16
17    task(2, 9);
18
19    std::cout << "task_lambda:\t" << result.get() << '\n';
20}
21
22void task_bind()
23{
24    std::packaged_task<int()> task(std::bind(f, 2, 11));
25    std::future<int> result = task.get_future();
26
27    task();
28
29    std::cout << "task_bind:\t" << result.get() << '\n';
30}
31
32void task_thread()
33{
34    std::packaged_task<int(int,int)> task(f);
35    std::future<int> result = task.get_future();
36
37    std::thread task_td(std::move(task), 2, 10);
38    task_td.join();
39
40    std::cout << "task_thread:\t" << result.get() << '\n';
41}
42
43int main()
44{
45    task_lambda();
46    task_bind();
47    task_thread();
48}

与 std::async 相比,std::packaged_task 不会直接执行任务,而是需要显式调用,因此可以做到将执行函数的时机和获取 future 的时机分离。

 1std::packaged_task<int()> task(sleep);
 2
 3auto f = task.get_future();
 4task(); // invoke the function
 5
 6// You have to wait until task returns. Since task calls sleep
 7// you will have to wait at least 1 second.
 8std::cout << "You can see this after 1 second\n";
 9
10// However, f.get() will be available, since task has already finished.
11std::cout << f.get() << std::endl;
12
13
14
15
16auto f = std::async(std::launch::async, sleep);
17std::cout << "You can see this immediately!\n";
18
19// However, the value of the future will be available after sleep has finished
20// so f.get() can block up to 1 second.
21std::cout << f.get() << "This will be shown after a second!\n";

std::promise
#

第一次用 C++11 写多线程就是用的 promise,参见 promise && future learning notes

个人认为 std::promise 与 std::async 或者 std::packaged_task 最明显的区别是,它的值不是函数的返回值,而是可以通过 set_value 来设置,因此更加灵活。

需要注意的是,std::promise 不支持拷贝构造,因此需要使用 std::move 或者传 promise 的指针。

In summary
#

  • async:提供最高层次的抽象。如果你不需要控制线程的运行时机,就选这个。
  • packaged_task:抽象层次比 async 低。如果你需要控制线程的运行时机,且线程执行的结果即目标结果时,选这个。
  • promise:抽象层次最低。当你想在线程中设置目标结果的值,选这个。

参考资料:

货比三家:C++ 中的 task based 并发

What is the difference between packaged_task and async

std::promise

相关文章

[C++11] promise && future leanrning notes

·322 字·1 分钟
用人话就是,主线程传给附属线程一个promise Object,然后主线程想要获取附属线程set给promise Object的值(也就是该线程返回的某个结果),需要通过主线程中的promise object 得到对应的future object(每个promise 对应一个 future),然后调用future 的get方法。如果附属线程没有执行作为参数传入的promise的set方法去返回结果,那么程序就会block住。