I'm fairly familiar with C++11's std::thread
, std::async
and std::future
components (e.g. see this answer), which are straight-forward.
However, I cannot quite grasp what std::promise
is, what it does and in which situations it is best used. The standard document itself doesn't contain a whole lot of information beyond its class synopsis, and neither does std::thread.
Could someone please give a brief, succinct example of a situation where an std::promise
is needed and where it is the most idiomatic solution?
A promise is an object that can store a value of type T to be retrieved by a future object (possibly in another thread), offering a synchronization point. On construction, promise objects are associated to a new shared state on which they can store either a value of type T or an exception derived from std::exception .
std::promise is non-copyable, so there's no way to stick a functor with this member into a std::function .
The class template std::future provides a mechanism to access the result of asynchronous operations: An asynchronous operation (created via std::async, std::packaged_task, or std::promise) can provide a std::future object to the creator of that asynchronous operation.
A future is an object that can retrieve a value from some provider object or function, properly synchronizing this access if in different threads. "Valid" futures are future objects associated to a shared state, and are constructed by calling one of the following functions: async.
I understand the situation a bit better now (in no small amount due to the answers here!), so I thought I add a little write-up of my own.
There are two distinct, though related, concepts in C++11: Asynchronous computation (a function that is called somewhere else), and concurrent execution (a thread, something that does work concurrently). The two are somewhat orthogonal concepts. Asynchronous computation is just a different flavour of function call, while a thread is an execution context. Threads are useful in their own right, but for the purpose of this discussion, I will treat them as an implementation detail.
There is a hierarchy of abstraction for asynchronous computation. For example's sake, suppose we have a function that takes some arguments:
int foo(double, char, bool);
First off, we have the template std::future<T>
, which represents a future value of type T
. The value can be retrieved via the member function get()
, which effectively synchronizes the program by waiting for the result. Alternatively, a future supports wait_for()
, which can be used to probe whether or not the result is already available. Futures should be thought of as the asynchronous drop-in replacement for ordinary return types. For our example function, we expect a std::future<int>
.
Now, on to the hierarchy, from highest to lowest level:
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.
Promises are intimately related to exceptions. The interface of a promise alone is not enough to convey its state completely, so exceptions are thrown whenever an operation on a promise does not make sense. All exceptions are of type std::future_error
, which derives from std::logic_error
. First off, a description of some constraints:
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.
Here is a little test series to demonstrate these various exceptional behaviours. First, the harness:
#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; } }
Now on to the tests.
Case 1: Inactive promise
int test() { std::promise<int> pr; return 0; } // fine, no problems
Case 2: Active promise, unused
int test() { std::promise<int> pr; auto fut = pr.get_future(); return 0; } // fine, no problems; fut.get() would block indefinitely
Case 3: Too many futures
int test() { std::promise<int> pr; auto fut1 = pr.get_future(); auto fut2 = pr.get_future(); // Error: "Future already retrieved" return 0; }
Case 4: Satisfied promise
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".
Case 5: Too much satisfaction
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(); }
The same exception is thrown if there is more than one of either of set_value
or set_exception
.
Case 6: Exception
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
Case 7: Broken promise
int test() { std::promise<int> pr; auto fut = pr.get_future(); { std::promise<int> pr2(std::move(pr)); } // Error: "broken promise" return fut.get(); }
In the words of [futures.state] a std::future
is an asynchronous return object ("an object that reads results from a shared state") and a std::promise
is an asynchronous provider ("an object that provides a result to a shared state") i.e. a promise is the thing that you set a result on, so that you can get it from the associated future.
The asynchronous provider is what initially creates the shared state that a future refers to. std::promise
is one type of asynchronous provider, std::packaged_task
is another, and the internal detail of std::async
is another. Each of those can create a shared state and give you a std::future
that shares that state, and can make the state ready.
std::async
is a higher-level convenience utility that gives you an asynchronous result object and internally takes care of creating the asynchronous provider and making the shared state ready when the task completes. You could emulate it with a std::packaged_task
(or std::bind
and a std::promise
) and a std::thread
but it's safer and easier to use 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 promise
s and associated future
s 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.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With