为了解释std::future的用途,《并发编程》一书用了现实的例子作类比:假设我们要坐飞机去国外度假,在到达机场并完成了各种手续后,我们还要等待机场登机的通知,可能需要数个小时。在此期间,我们可以看书、上网或者喝咖啡,同时等待登机信号。标准库使用std::future为类似的事件建模,如果线程需要等待特定的一次性事件,它就会获取一个future代表这一事件,然后该线程可以周期性地在这个future上等待一小段时间来检查事件是否发生(检查告示牌),而在检查间隙执行其他任务(喝咖啡)。如果它执行了其他任务,知道其所需的事件已发生才会继续进行原任务,随后等待future就绪。一旦事件发生,即future就绪,它就无法复位。
标准库提供两种future,唯一的std::future<>和可以被共享的std::shared_future<>,分别根据std::unique_ptrstd::shared_ptr实现。前者的实例对应的关联事件仅被该实例拥有,后者的则能被多个std::shared_future共享。虽然std::future被应用于线程间通信,但它本身不提供同步访问,如果多个线程需要访问同一个future对象,仍然需要互斥元或同步机制保护访问。

从后台任务中返回值

假设我们有一个长期运行计算的任务,计算最终会得到一个有用的结果。我们可以启动一个新的线程来执行计算,但必须将结果传回来。std::thread没有提供这样的机制,我们需要借助std::async。在不需要立刻得到结果的说以后,可以使用std::async启动一个异步任务。std::async返回一个std::future对象,而不是一个std::thread对象让你在上面等待,std::future对象将持有计算的返回值。当需要这个值时,只要在future上调用get(),线程就会阻塞直到future就绪,然后返回该值。看下面的实例:

#include <iostream>
#include <future>

int find_the_answer_to_ltuae();
void do_other_stuff();
int main()
{
    std::future<int> the_answer = std::async(find_the_answer_to_ltuae);
    do_other_stuff();
    std::cout << "The answer is " << the_answer.get() << std::endl;
}

std::async的使用和std::future类似,如果第一个参数是指向成员函数的指针,第二个参数则提供了该函数的独享实例,其余的参数则是该函数的参数。如果第一个参数不是成员函数,第二个参数及以后则是该函数的参数。如果参数是右值,则通过移动原来的参数创造副本。看下面的代码:

#include <string>
#include <future>
struct X
{
    void foo(int, std::string const&);
    std::string bar(std::string const&);
};
X x;
auto f1 = std::async(&X::foo, &x, 42, "hello"); //调用的是x.foo()
auto f2 = std::async(&X::bar, x, "goodbye");    //调用的是tmpx.bar()
struct Y
{
    double operator()(double);
};
Y y;
auto f3 = std::async(Y(), 3.141);         //调用的是tmpy(3.141)
auto f4 = std::async(std::ref(y), 2.718); //调用的是y(2.718)
X baz(X&);
std::async(baz, std::ref(x));
class move_only
{
public:
    move_only();
    move_only(move_only&&);
    move_only(move_only const&) = delete;
    move_only& operator=(move_only&&);
    move_only& operator=(move_only const&) = delete;

   void operator()();
};
auto f5 = std::async(move_only());

std::async是否启动一个新线程,或者在等待future时是否同步运行都取决于具体实现方式,但我们可以在调用时指定额外的参数来调整。这个参数可以是std::launch类型,如std::launch::defered,表明函数调用将会延迟,直到在future调用wait()get()位置;也可以是std::launch::async,表示该函数必须运行在自己的线程上;也可以是std::launch::defered | std::launch::async,表示由具体实现决定。如果是第一种情况,即延迟调用,函数实际上可能永远不会运行,例如:

auto f6 = std::async(std::launch::async, Y(), 1.2); //在新线程中运行
auto f7 = std::async(std::launch::defered, baz, std::ref(x)); //在wait()或get()中运行
auto f8 = std::async(std::launch::defered | std::launch::async, baz, std::ref(x)); //由具体实现决定
auto f9 = std::async(baz, std::ref(x)); //由具体实现决定
f7.wait(); //延迟调用

将任务与future相关联

std::packaged_task<>将一个future绑定到一个可调用对象上。当std::packaged_task<>对象被调用时,它就调用关联的可调用对象,让future就绪,将返回值作为关联数储存。std::packaged_task<>类模板的参数为函数签名,比如void()表示无参数无返回值的函数,或是int(std::string&, double*)表示接收对std::string的非const引用和指向double的只恨,并返回int的函数。返回类型确定了从get_future()成员函数返回的std::future<>的类型,函数签名的参数列表则指定了operator()的参数类型。例如,一个特化的std::packaged_task<std::string(std::vector<char>*, int)>的部分定义如下:

template<>
class packaged_task<std::string(std::vector<char>*, int)>
{
public:
    template<typename Callble>
    explicit packaged_task(Callble&& f);
    std::future<std::string> get_future();
    void operator()(std::vector<char>*, int);
};

它是一个可调用对象,可以被封装入一个std::function,作为线程函数传给std::thread,或者传给需要可调用对象的另一个函数,或者被直接调用。当std::packaged_task作为函数对象被调用时,提供给operator()的参数被传给所包含的函数,并且将返回值作为异步结果,存放在get_future()获取的std::future中。因此可以将任务封装在std::packaged_task中,并且从中获取future,在需要结果时等待future就绪。看下面的例子:

#include <deque>
#include <mutex>
#include <future>
#include <thread>
#include <utility>

std::mutex m;
std::deque<std::packaged_task<void()>> tasks;

bool gui_shutdown_message_received();
void get_and_process_gui_message();

void gui_thread()
{
    while(!gui_shutdown_message_received())
    {
        get_and_process_gui_message();
        std::packaged_task<void()> task;
        {
            std::lock_gurad<std::mutex> lk(m);
            if(tasks.empty())
                continue;
            task = std::move(tasks.front()); // 从队列中提取任务
            tasks.pop_front();
        }
        task(); // 释放锁,运行任务。完成后future就绪
    }
}

std::thread gui_bg_thread(gui_thread);

templace<typename Func>
std::future<void> post_task_for_gui_thread(Func f)
{
    std::packaged_task<void()> task(f); // 创建新任务
    std::future<void> res = task.get_future(); // 获取future
    std::lock_guard<std::mutex> lk(m);
    tasks.push_back(std::move(task)); // 将任务置于队列
    return res; // 返回future
}