# std::async/std::promise和std::packaged_task

# std::async

# 介绍

std::async是一个执行异步任务的函数模板,其函数运行在从线程池获取的独立线程上。

std::async的放回值是一个std::future对象,通过get方法获取函数的返回值。

std::async支持的运行策略有:

  • std::launch::async (opens new window)std::async 可以在后台启动一个新的线程(或者从线程池中获取一个线程),异步执行指定的函数 f。它返回一个 std::future对象,最终将保存函数调用的结果。
  • std::launch::deferred (opens new window),std::async 还支持延迟求值。如果设置了 std::launch::deferred 标志,函数 f 不会立即执行,而是在调用 std::futureget()wait() 方法时才执行。

函数f运行结束和std::async返回的future对象使用的共享状态值修改是串行的。

# 实例

#include <future>
#include <thread>
#include <iostream>

void asyncFunc(){
    std::cout << "do right now in a new thread...\n";
}

void deferFunc(){
    std::cout << "doing refered...\n";
}
 
int main()
{   
    auto fut1 = std::async(std::launch::async, asyncFunc);
    auto fut2 = std::async(std::launch::async, deferFunc);
    std::cout << "before fut1 get.\n";
    fut2.get();
    std::cout << "after fut2 get.\n";
    fut1.get();
}

// do right now in a new thread...
// before fut1 get.
// doing refered...
// after fut2 get.

上面可以看到,使用std::launch::async时,函数立刻就开始执行了,而使用std::launch::async是到使用std::future.get方法时,函数才执行,起到了延时执行的效果。

当使用std::launch::async时,会创建新的线程,使用ps -T -p <PID>可以查看进程下启动的线程。

ps -T -p 363107
#     PID    SPID TTY          TIME CMD
#  363107  363107 pts/2    00:00:00 af
#  363107  363108 pts/2    00:00:00 af

SPID行表示的是Stack Pointer ID进程下的栈指针ID,也就是linux内核意义上的进程。

# std::promise

# std::promise介绍

std::promiseC++11中引人的模板类,非空特化可以用来在线程之间交换对象,void特化可以用来在调度状态无关的事件。

std::promise用于异步的存储一个值或异常,并通过返回一个std::future对象来获取对应的值。它允许一个线程(通常是生产者线程)承诺将某个值或异常传递给另一个线程(通常是消费者线程)。

# std::promise实例

#include <future>
#include <thread>
#include <iostream>
#include <vector>
#include <numeric>

void accAdd(std::vector<int>::iterator begin,
            std::vector<int>::iterator end,
            std::promise<int> promise) 
{
    int sum = std::accumulate(begin, end, 0);
    promise.set_value(sum);
}

void voidFunc(std::promise<void> promise) {
    std::this_thread::sleep_for(std::chrono::seconds(2));
    promise.set_value();
}
 
int main()
{   

    std::vector<int> nums{0, 1, 2, 3, 4, 5, 6, 7, 8, 9};
    std::promise<int> accumulated_promise;
    std::future<int> accumulated_future = accumulated_promise.get_future();
    std::thread t1(accAdd, nums.begin(), nums.end(), std::move(accumulated_promise));
    t1.join();
    std::cout << "accumulated_future: " << accumulated_future.get() << std::endl;

    std::promise<void> barrier_promise;
    std::future<void> barrier_future = barrier_promise.get_future();
    std::thread t2(voidFunc, std::move(barrier_promise));
    std::cout << "barrier thread is waiting set_value event...\n";
    barrier_future.wait();
    std::cout << "barrier thread waiting job finished.\n";
    t2.join();
}

上面的代码可以看到std::promise的使用方式,先定一个std::promise对象,然后通过移动语义将其传入到线程中,在线程的任务函数中调用其set_value方法赋值,结合std::future来阻塞获取promise的返回值。

# std::packaged_task

# std::packaged_task介绍

std::packaged_task也是一个类模板,模板特化使用的参数是函数的签名,接受可调用参数(函数/lambda表达式/bind表达式或函数对象)作为参数以把可调用函数转换成异步任务。其函数返回值和抛出的异常保存在一个共享状态中,通过std::future对象来获取共享状态的值。

# std::packaged_task实例

#include <future>
#include <thread>
#include <iostream>
#include <vector>
#include <numeric>
#include <functional>

void task_lambda()
{
    std::packaged_task<int(int, int)> task([](int a, int b){
        return a + b;
    });

    std::future<int> task_future = task.get_future();
    task(11, 23);
    std::cout << "task_lambda:\t" << task_future.get() << '\n';
}

void task_bind()
{
    auto f = std::bind(pkg_task_add, 13, 15);
    std::packaged_task<int()> task(f);
    std::future<int> task_future = task.get_future();
    task();
    std::cout << "task_bind:\t" << task_future.get() << '\n';
}

void task_thread()
{
    std::packaged_task<int(int, int)> task(pkg_task_add);
    std::future<int> task_future = task.get_future();
    std::thread task_td(std::move(task), 2, 10);
    task_td.join();
    std::cout << "task_thread done, result is ready. waiting for waking up...\n";
    std::this_thread::sleep_for(std::chrono::seconds(10));
    std::cout << "task_thread:\t" << task_future.get() << '\n';
}

int main()
{   
    task_lambda();
    task_bind();
    task_thread();
}

使用std::packaged_task对函数进行封装后,可以借用std::future对象将值的获取和函数的运行分离,在先调用函数,然后调用关联future对象的get方法获取函数的运行结果。

# reference

1.https://en.cppreference.com/w/cpp/thread/async (opens new window)
2.https://en.cppreference.com/w/cpp/thread/promise (opens new window)
3.https://en.cppreference.com/w/cpp/thread/packaged_task (opens new window)