我相当熟悉c++ 11的std::thread, std::async和std::future组件(例如,请看这个答案),这些都很简单。

然而,我不能完全理解std::promise是什么,它做什么以及在什么情况下最好使用它。标准文档本身除了类概要之外不包含大量信息,std::thread也不包含。

谁能给一个简要的例子,说明需要std::promise的情况,以及它是最常用的解决方案的情况?


当前回答

用[期货]的话说。一个std::future是一个异步返回对象(“一个从共享状态读取结果的对象”),一个std::promise是一个异步提供者(“一个为共享状态提供结果的对象”),即一个promise是你设置结果的对象,这样你就可以从关联的future中获得它。

异步提供程序最初创建future引用的共享状态。Std::promise是异步提供程序的一种类型,Std::packaged_task是另一种类型,Std::async的内部细节是另一种类型。它们中的每一个都可以创建一个共享状态,并为您提供一个共享该状态的std::future,并可以使该状态就绪。

async是一个更高级的便利实用程序,它为您提供一个异步结果对象,并在内部负责创建异步提供程序,并在任务完成时准备好共享状态。你可以用std::packaged_task(或std::bind和std::promise)和std::thread来模拟它,但使用std::async更安全、更容易。

std::promise is a bit lower-level, for when you want to pass an asynchronous result to the future, but the code that makes the result ready cannot be wrapped up in a single function suitable for passing to std::async. For example, you might have an array of several promises and associated futures and have a single thread which does several calculations and sets a result on each promise. async would only allow you to return a single result, to return several you would need to call async several times, which might waste resources.

其他回答

现在我对这种情况有了更好的理解(由于这里的答案,这不是一小部分!),所以我想我添加了一些我自己的文章。


c++ 11中有两个截然不同但又相关的概念:异步计算(在其他地方调用的函数)和并发执行(线程,并行工作的东西)。这两个概念在某种程度上是正交的。异步计算只是一种不同风格的函数调用,而线程是一个执行上下文。线程本身是有用的,但是为了讨论的目的,我将把它们作为一个实现细节。

异步计算有一个抽象层次。举个例子,假设我们有一个带参数的函数:

int foo(double, char, bool);

首先,我们有模板std::future<T>,它表示类型为T的未来值。该值可以通过成员函数get()检索,该函数通过等待结果有效地同步程序。或者,future支持wait_for(),它可用于探测结果是否已经可用。期货应该被认为是普通返回类型的异步插入式替换。对于我们的示例函数,我们期望std::future<int>。

现在,在层次结构上,从最高到最低的层次:

std::async: The most convenient and straight-forward way to perform an asynchronous com­pu­ta­tion is via the async function template, which returns the matching future immediately: auto fut = std::async(foo, 1.5, 'x', false); // is a std::future<int> We have very little control over the details. In particular, we don't even know if the function is exe­cu­ted concurrently, serially upon get(), or by some other black magic. However, the result is easily ob­tained when needed: auto res = fut.get(); // is an int We can now consider how to implement something like async, but in a fashion that we control. For example, we may insist that the function be executed in a separate thread. We already know that we can provide a separate thread by means of the std::thread class. The next lower level of abstraction does exactly that: std::packaged_task. This is a template that wraps a function and provides a future for the functions return value, but the object itself is call­able, and calling it is at the user's discretion. We can set it up like this: std::packaged_task<int(double, char, bool)> tsk(foo); auto fut = tsk.get_future(); // is a std::future<int> The future becomes ready once we call the task and the call completes. This is the ideal job for a se­pa­rate thread. We just have to make sure to move the task into the thread: std::thread thr(std::move(tsk), 1.5, 'x', false); The thread starts running immediately. We can either detach it, or have join it at the end of the scope, or whenever (e.g. using Anthony Williams's scoped_thread wrapper, which really should be in the standard library). The details of using std::thread don't concern us here, though; just be sure to join or detach thr eventually. What matters is that whenever the function call finishes, our result is ready: auto res = fut.get(); // as before Now we're down to the lowest level: How would we implement the packaged task? This is where the std::promise comes in. The promise is the building block for communicating with a future. The principal steps are these: The calling thread makes a promise. The calling thread obtains a future from the promise. The promise, along with function arguments, are moved into a separate thread. The new thread executes the function and fulfills the promise. The original thread retrieves the result. As an example, here's our very own "packaged task": template <typename> class my_task; template <typename R, typename ...Args> class my_task<R(Args...)> { std::function<R(Args...)> fn; std::promise<R> pr; // the promise of the result public: template <typename ...Ts> explicit my_task(Ts &&... ts) : fn(std::forward<Ts>(ts)...) { } template <typename ...Ts> void operator()(Ts &&... ts) { pr.set_value(fn(std::forward<Ts>(ts)...)); // fulfill the promise } std::future<R> get_future() { return pr.get_future(); } // disable copy, default move }; Usage of this template is essentially the same as that of std::packaged_task. Note that moving the entire task subsumes moving the promise. In more ad-hoc situations, one could also move a promise object explicitly into the new thread and make it a function argument of the thread function, but a task wrapper like the one above seems like a more flexible and less intrusive solution.


生产异常

承诺与异常密切相关。promise的接口本身不足以完全传达它的状态,因此每当promise上的操作没有意义时,就会抛出异常。所有异常都是std::future_error类型,它起源于std::logic_error。首先,一些约束条件的描述:

A default-constructed promise is inactive. Inactive promises can die without consequence. A promise becomes active when a future is obtained via get_future(). However, only one future may be obtained! A promise must either be satisfied via set_value() or have an exception set via set_exception() before its lifetime ends if its future is to be consumed. A satisfied promise can die without consequence, and get() becomes available on the future. A promise with an exception will raise the stored exception upon call of get() on the future. If the promise dies with neither value nor exception, calling get() on the future will raise a "broken promise" exception.

下面是一个小测试系列来演示这些不同的异常行为。首先,挽具:

#include <iostream>
#include <future>
#include <exception>
#include <stdexcept>

int test();

int main()
{
    try
    {
        return test();
    }
    catch (std::future_error const & e)
    {
        std::cout << "Future error: " << e.what() << " / " << e.code() << std::endl;
    }
    catch (std::exception const & e)
    {
        std::cout << "Standard exception: " << e.what() << std::endl;
    }
    catch (...)
    {
        std::cout << "Unknown exception." << std::endl;
    }
}

现在来看看测试。

案例1:非活动承诺

int test()
{
    std::promise<int> pr;
    return 0;
}
// fine, no problems

案例2:主动承诺,未使用

int test()
{
    std::promise<int> pr;
    auto fut = pr.get_future();
    return 0;
}
// fine, no problems; fut.get() would block indefinitely

案例3:太多的未来

int test()
{
    std::promise<int> pr;
    auto fut1 = pr.get_future();
    auto fut2 = pr.get_future();  //   Error: "Future already retrieved"
    return 0;
}

案例4:兑现承诺

int test()
{
    std::promise<int> pr;
    auto fut = pr.get_future();

    {
        std::promise<int> pr2(std::move(pr));
        pr2.set_value(10);
    }

    return fut.get();
}
// Fine, returns "10".

案例5:过于满足

int test()
{
    std::promise<int> pr;
    auto fut = pr.get_future();

    {
        std::promise<int> pr2(std::move(pr));
        pr2.set_value(10);
        pr2.set_value(10);  // Error: "Promise already satisfied"
    }

    return fut.get();
}

如果set_value或set_exception的值多于一个,则会抛出相同的异常。

案例6:例外

int test()
{
    std::promise<int> pr;
    auto fut = pr.get_future();

    {
        std::promise<int> pr2(std::move(pr));
        pr2.set_exception(std::make_exception_ptr(std::runtime_error("Booboo")));
    }

    return fut.get();
}
// throws the runtime_error exception

案例7:失信

int test()
{
    std::promise<int> pr;
    auto fut = pr.get_future();

    {
        std::promise<int> pr2(std::move(pr));
    }   // Error: "broken promise"

    return fut.get();
}

promise是async函数返回信息的通道或路径。Std::future是一种同步机制,它使调用者等待Std::promise中携带的返回值准备好(意味着它的值在函数内部设置)。

承诺是电线的另一端。

假设您需要检索由异步程序计算的future的值。然而,你不希望它在同一个线程中计算,你甚至不“现在”生成一个线程——也许你的软件被设计为从线程池中选择一个线程,所以你不知道谁将在最后执行这些计算。

现在,你传递什么给这个(未知的)线程/类/实体?你不通过将来,因为这是结果。你想要传递一些连接到未来的东西,它代表导线的另一端,所以你只会查询未来,而不知道谁会实际计算/写入一些东西。

这就是承诺。它是连接你未来的手柄。如果future是一个演讲者,并且使用get()开始听直到一些声音出来,那么promise就是一个麦克风;但不是普通的麦克风,它是通过一根电线连接到你手中的扬声器的麦克风。你可能知道电话的另一端是谁,但你不需要知道——你只需要把电话给对方,然后等对方开口。

在异步处理中有3个核心实体。c++ 11目前专注于其中的两个。

异步运行逻辑所需要的核心内容是:

任务(逻辑打包为某个函子对象)将运行“某处”。 实际的处理节点——线程、进程等,当这些函子被提供给它时运行它们。查看“Command”设计模式,了解基本工作线程池是如何做到这一点的。 结果句柄:有人需要这个结果,需要一个对象来为他们获取结果。出于OOP和其他原因,任何等待或同步都应该在这个句柄的api中完成。

c++ 11调用了我在(1)std::promise和(3)std::future中提到的东西。 std::thread是(2)唯一公开提供的东西。这是不幸的,因为真正的程序需要管理线程和内存资源,大多数程序都希望任务运行在线程池上,而不是为每个小任务创建和销毁一个线程(这几乎总是会导致不必要的性能损失,并且很容易造成更糟糕的资源短缺)。

根据Herb Sutter和c++ 11智囊团的其他人的说法,他们暂定计划添加std::executor,这很像Java,将是线程池的基础,逻辑上类似于(2)的设置。也许我们会在c++ 2014中看到它,但我打赌更像c++ 17(如果他们搞砸了这些标准,上帝会帮助我们)。

用[期货]的话说。一个std::future是一个异步返回对象(“一个从共享状态读取结果的对象”),一个std::promise是一个异步提供者(“一个为共享状态提供结果的对象”),即一个promise是你设置结果的对象,这样你就可以从关联的future中获得它。

异步提供程序最初创建future引用的共享状态。Std::promise是异步提供程序的一种类型,Std::packaged_task是另一种类型,Std::async的内部细节是另一种类型。它们中的每一个都可以创建一个共享状态,并为您提供一个共享该状态的std::future,并可以使该状态就绪。

async是一个更高级的便利实用程序,它为您提供一个异步结果对象,并在内部负责创建异步提供程序,并在任务完成时准备好共享状态。你可以用std::packaged_task(或std::bind和std::promise)和std::thread来模拟它,但使用std::async更安全、更容易。

std::promise is a bit lower-level, for when you want to pass an asynchronous result to the future, but the code that makes the result ready cannot be wrapped up in a single function suitable for passing to std::async. For example, you might have an array of several promises and associated futures and have a single thread which does several calculations and sets a result on each promise. async would only allow you to return a single result, to return several you would need to call async several times, which might waste resources.