我相当熟悉c++ 11的std::thread, std::async和std::future组件(例如,请看这个答案),这些都很简单。
然而,我不能完全理解std::promise是什么,它做什么以及在什么情况下最好使用它。标准文档本身除了类概要之外不包含大量信息,std::thread也不包含。
谁能给一个简要的例子,说明需要std::promise的情况,以及它是最常用的解决方案的情况?
我相当熟悉c++ 11的std::thread, std::async和std::future组件(例如,请看这个答案),这些都很简单。
然而,我不能完全理解std::promise是什么,它做什么以及在什么情况下最好使用它。标准文档本身除了类概要之外不包含大量信息,std::thread也不包含。
谁能给一个简要的例子,说明需要std::promise的情况,以及它是最常用的解决方案的情况?
当前回答
在一个粗略的近似中,你可以把std::promise看作std::future的另一端(这是错误的,但是为了说明问题,你可以把它看作是)。通信通道的消费者端将使用std::future来消费来自共享状态的数据,而生产者线程将使用std::promise来写入共享状态。
其他回答
在一个粗略的近似中,你可以把std::promise看作std::future的另一端(这是错误的,但是为了说明问题,你可以把它看作是)。通信通道的消费者端将使用std::future来消费来自共享状态的数据,而生产者线程将使用std::promise来写入共享状态。
Bartosz milwski提供了一个很好的记录。
c++将期货的实现划分为一个集合 小块的
性病:承诺是这些部分之一。
承诺是传递返回值(或对象)的载体 异常)从执行函数的线程到线程 这就利用了未来的功能。
...
对象周围构造的同步对象 承诺信道的接收端。
所以,如果你想使用一个未来,你最终会得到一个承诺,你用它来获得异步处理的结果。
该页面的一个例子是:
promise<int> intPromise;
future<int> intFuture = intPromise.get_future();
std::thread t(asyncFun, std::move(intPromise));
// do some other stuff
int result = intFuture.get(); // may throw MyException
在异步处理中有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(如果他们搞砸了这些标准,上帝会帮助我们)。
promise是async函数返回信息的通道或路径。Std::future是一种同步机制,它使调用者等待Std::promise中携带的返回值准备好(意味着它的值在函数内部设置)。
现在我对这种情况有了更好的理解(由于这里的答案,这不是一小部分!),所以我想我添加了一些我自己的文章。
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 computation 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 executed concurrently, serially upon get(), or by some other black magic. However, the result is easily obtained 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 callable, 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 separate 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();
}