What is std::promise?
Solution 1
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.
Solution 2
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 theasync
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 thestd::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 havejoin
it at the end of the scope, or whenever (e.g. using Anthony Williams'sscoped_thread
wrapper, which really should be in the standard library). The details of usingstd::thread
don't concern us here, though; just be sure to join or detachthr
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.
Making exceptions
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 viaset_exception()
before its lifetime ends if its future is to be consumed. A satisfied promise can die without consequence, andget()
becomes available on the future. A promise with an exception will raise the stored exception upon call ofget()
on the future. If the promise dies with neither value nor exception, callingget()
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();
}
Solution 3
Bartosz Milewski provides a good writeup.
C++ splits the implementation of futures into a set of small blocks
std::promise is one of these parts.
A promise is a vehicle for passing the return value (or an exception) from the thread executing a function to the thread that cashes in on the function future.
...
A future is the synchronization object constructed around the receiving end of the promise channel.
So, if you want to use a future, you end up with a promise that you use to get the result of the asynchronous processing.
An example from the page is:
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
Solution 4
In a rough approximation you can consider std::promise
as the other end of a std::future
(this is false, but for illustration you can think as if it was). The consumer end of the communication channel would use a std::future
to consume the datum from the shared state, while the producer thread would use a std::promise
to write to the shared state.
Solution 5
std::promise
is the channel or pathway for information to be returned from the async function. std::future
is the synchronization mechanism thats makes the caller wait until the return value carried in the std::promise
is ready(meaning its value is set inside the function).
Kerrek SB
Updated on July 08, 2022Comments
-
Kerrek SB almost 2 years
I'm fairly familiar with C++11's
std::thread
,std::async
andstd::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? -
Kerrek SB almost 12 yearsOK, but how would I set something like that up, and how is this different from
std::async
? -
David Rodríguez - dribeas almost 12 years@KerrekSB:
std::async
can conceptually (this is not mandated by the standard) understood as a function that creates astd::promise
, pushes that into a thread pool (of sorts, might be a thread pool, might be a new thread, ...) and returns the associatedstd::future
to the caller. On the client side you would wait on thestd::future
and a thread on the other end would compute the result and store it in thestd::promise
. Note: the standard requires the shared state and thestd::future
but not the existence of astd::promise
in this particular use case. -
Kerrek SB almost 12 yearsSeeing the promise in the thread's constructor finally made the penny drop. Bartosz's article is maybe not the greatest, but it does explain how the elements tie together. Thanks.
-
Kerrek SB almost 12 yearsI think I finally got it. I'll post some code for future reference (no pun intended).
-
Kerrek SB almost 12 yearsOK, so can I implement an
std:async(func, myargs...)
with a thread? Like so:std::promise<R> pr; std::future<R> ft = pr.get_future(); std::thread t([](std::promise<R> p, Args ...args) { p.set_value(func(args...)); }, std::move(pr), myargs...);
? Who joins the thread? How does the future'sget()
know that it has to calljoin
on the thread? -
David Rodríguez - dribeas almost 12 years@KerrekSB:
std::future
will not calljoin
on the thread, it has a pointer to a shared state which is the actual communication buffer. The shared state has a synchronization mechanism (probablystd::function
+std::condition_variable
to lock the caller until thestd::promise
is fulfilled. The execution of the thread is orthogonal to all this, and in many implementations you might find thatstd::async
are not executed by new threads that are then joined, but rather by a thread pool whose lifetime extends until the end of the program. -
Marc Mutz - mmutz almost 12 years@DavidRodríguez-dribeas: please edit the information from the comments into your answer.
-
Jonathan Wakely almost 12 years@David,
std::async(std::launch::async, ...)
must behave as if executed in a new thread, not a thread pool. See [futures.async], "as if in a new thread of execution represented by a thread object [...] a call to a waiting function on an asynchronous return object that shares the shared state created by this async call shall block until the associated thread has completed, as if joined" (emphasis mine) -
David Rodríguez - dribeas almost 12 years@JonathanWakely: That does not mean that it has to be executed in a new thread, only that it has to be executed asynchronously as-if it was run in a newly created thread. The main advantage of
std::async
is that the runtime library can make the right decisions for you with respect to the number of threads to create and in most cases I will expect runtimes that use thread pools. Currently VS2012 does use a thread pool under the hood, and it does not violate the as-if rule. Note that there are very little guarantees that need to be fulfilled for this particular as-if. -
Jonathan Wakely almost 12 yearsThread locals need to be re-initialized, but the as-if rule allows anything (which is why I put "as if" in italics :)
-
Puppy over 11 yearsMight waste resources? Might be incorrect, if that code cannot be parallelized.
-
Nawaz about 11 yearsYou said "...which effectively synchronizes the program by waiting for the result.". What does "synchronizes" mean here? What does the entire statement mean? I'm unable to understand this. None of the meaning of "synchronize" from this dictionary entry helps me understand the sentence. Does just "waiting" mean "synchronization"? Does every wait synchronize? I think I partially understand what you mean, but I'm not sure what you actually mean.
-
Kerrek SB about 11 years@Nawaz: "synchronizing" in the sense of serializing execution in the abstract machine... does that make sense?
-
Nawaz about 11 years@KerrekSB: Can you please elaborate on it a bit more?
-
Kerrek SB about 11 years@Nawaz: It's about how you model concurrent execution mentally. You think of executing all the statements in some interleaved sequence, but any serialization is valid -- except when you have a synchronisation point, which enforces constraints on the serialization. The standard actually defines the concept of "synchronizes-with" in this regard. I believe that's the sense which I had in mind (although it's been a while and I'd have to re-read what I wrote!).
-
Nawaz about 11 years@KerrekSB: Thanks. I was looking for the phrase "synchronizes-with". Now that makes sense to me.
std::future<T>::get()
actually synchronizes-with the current thread and to do that it may has to wait . That is how I understand asynchronous computation and synchronization (with the current thread). -
Nawaz about 11 years@KerrekSB: So I think "...which effectively synchronizes the asynchronous call with the current thread by (optionally) waiting for the result." makes more sense, as it is precisely worded. BTW, this answer deserves a blog. Do you blog?
-
Kerrek SB about 11 years@Nawaz: Yeah, that would be more precise, but I thought my shorter statement would make sense in context... of course it doesn't synchronise everything :-) I don't usually, blog I'm afraid. Maybe I could guest-write for RMF's blog?
-
StereoMatching over 10 yearsNice answer, thanks for your help.About the part of std::async, I remember that we could determine it would spawn another thread or work synchronous with flag(std::launch::async, std::launch::deferred)
-
Kerrek SB over 10 years@StereoMatching: Yes, those flags exist, though I'm not sure right now if they're binding of if they're only a hint. I believe a platform without threading support could still offer
async
and just perform the operation serially, though I'd have to check whether the launch flags make any guarantees. -
Felix Dombek over 10 yearsIn
my_task
, are you sure thatexplicit my_task(Ts &&... ts) : fn(std::forward<Ts>(ts)...) { }
is correct? Wouldn't the constructor just take a function (pointer) and initialize the internalstd::function
with it? Why are you forwarding multiple arguments? -
Kerrek SB over 10 years@FelixDombek: Perfect forwarding etc.
std::function
has many constructors; no reason not to expose those to the consumer ofmy_task
. -
Rapptz over 9 years@KerrekSB They're binding. Anything other than the two flags (
std::launch::async
andstd::launch::deferred
, along with not providing a policy at all) are implementation defined and non-portable. -
Daveed V. about 9 years@KerrekSB: Thanks for the write-up! A nit: << A promise with an exception will raise the stored exception upon call of
get()
on the future, while a promise with neither value nor exception will raise a "broken promise" exception. >> That makes it sound as if if callingget()
on a future whose associated promise hasn't been satisfied yet would raise the broken_promise exception. I think it usually will just block until (hopefully) the promise is satisfied. If the promise is destroyed, it "abandons" the state shared with the future, which records the broken_promise exception. -
Kerrek SB about 9 years@DaveedV.: Thanks for the feedback! Yes, that is test case 7: If you destroy the promise without setting either value or exception, then calling
get()
on the future raises an exception. I will clarify this by adding "before it is destroyed"; please let me know if that's sufficiently clear. -
sunny moon about 7 yearsFinally
got()
myfuture
of grokking the thread support library on thepromise
of your amazing explanation! -
Mudit Jain over 6 yearsWouldn't case 2 result in broken promise exception since pr was destroyed after return of test() similar to case 7?
-
einpoklum about 5 years"asynchronous return" and "reads result from shared state" are mostly orthogonal, which makes the first sentence a bit confusing. Do you mean to say that the sharing of the state is between the future and the promise? If so, please say that explicitly from the outset.
-
Jonathan Wakely about 5 years@einpoklum why did you stop reading "asynchronous return object" before the last word? I'm quoting the terminology of the standard. A
future
is a concrete example of an asynchronous return object, which is an object that reads a result that was returned asynchronously, via the shared state. Apromise
is a concrete example of an asynchronous provider, which is an object that writes a value to the shared state, which can be read asynchronously. I meant what I wrote. -
madhur4127 over 2 years"using Anthony Williams's scoped_thread wrapper, which really should be in the standard library" this is wrong as of C++20, there's
std::jthread