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

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

此外,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
 8    int f(int x, int y) { return std::pow(x,y); }
 9     
10    void 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     
22    void 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     
32    void 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     
43    int main()
44    {
45        task_lambda();
46        task_bind();
47        task_thread();
48    }

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

 1    std::packaged_task<int()> task(sleep);
 2    
 3    auto f = task.get_future();
 4    task(); // 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.
 8    std::cout << "You can see this after 1 second\n";
 9    
10    // However, f.get() will be available, since task has already finished.
11    std::cout << f.get() << std::endl;
12
13
14
15    
16    auto f = std::async(std::launch::async, sleep);
17    std::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.
21    std::cout << f.get() << "This will be shown after a second!\n";
22
23

std::promise

第一次用c++11写多线程就是用的promisepromise && future leanrning 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