Today I would like to introduce the C++ threaded high-level APIs:
std::async. The content of this article can be condensed into the following diagram.
std::future are synchronisation channels between threads. The
std::packed_task class template is an adapter for a
function or a
function object. It wraps the return value of a function in
std::future, allowing us to easily execute any function with
std::async function is equivalent to the sum of
I will then introduce each API in turn.
- std::promise and std::future
std::promise and std::future
std::future<T> class templates are defined in the
<future> header file. Together they form a synchronisation channel. Where
std::promise is the sender and
std::future is the receiver. The specific usage is as follows.
In the above example, we first create a
std::promise<int> object. Where the
int pattern argument means that this synchronous channel will pass an
int object. Next, we call
p.get_future() to get the receiver. We then pass
p.set_value(42) and get the object with
Next, let’s add a thread.
In this example, we hand over
std::promise<int> to another thread, which passes the
int object, and the main thread reads the
int object with
f.get(). If the master thread executes to
f.get() first, the master thread will wait for the other thread until
p.set_value(42) is finished.
Also, this sync channel can only be used once. If we call
f.set_value(...)' multiple times or
f.get() they will throw
Strictly speaking, calling
std::promise<T>::set_value()multiple times is an Undefined Behavior. However, the C++ standard encourages C++ authors to throw in
std::future_erroras an exception.
wait member function
On the receiver side we can split the receiver ‘wait’ and ‘read’ into two steps.
std::future<T> has three member functions.
void wait(): waits until the object is ready to be read.
future_status wait_for(const std::chrono::duration<... > &): Wait until the object is ready to be read or until all the wait time has been used.
future_status wait_until(const std::chrono::time_point<... > &): Wait until the object can be read or the deadline is reached.
The return values for the last two functions can be.
std::futureobject corresponds to the sending end of an inert evaluation (see the
future_status::ready: The object is ready to be read.
future_status::timeout: Waiting for timeout.
For example, if another thread takes a while to produce a return value, and we want the main thread to print something while waiting, we can use the
wait_for member function.
Another situation is where we just want to synchronise the point in time when the execution starts with
std::future, and we don’t really want to send an object. In this case we can use the
wait member function.
We can also use
std::promise to send an Exception. If the sender wants to send an “exception” to the receiver, we can call
std::promise<T>::set_exception. When the receiver calls
get() function will again throw the exception.
It is worth noting that
p.set_exception(...) has the argument type
std::exception_ptr, so we cannot pass the
std::runtime_error object instance directly. We must first throw the exception with a
throw statement, then get
std::current_exception() in the
catch clause and call
set_exception to send the exception.
Although the purpose of
std::promise is to pass objects between threads. However,
std::future objects cannot be manipulated by multiple threads at the same time. For example, in the following example, the threads
f.get() at the same time. However, because the
get() member function itself is not entirely Thread-safe, the following code will have undefined behaviour (and generate a Segmentation Fault on my machine).
The solution is also very simple. If we want two (or more) threads to be receivers at the same time, we should first call the
share() member function of
std::future. This will convert the
std::future object to a
std::shared_future object. We then copy the
std::shared_future object so that
t2 threads each have a copy.
std::packed_task class template is also defined in the
<future> header file. Its purpose is to act as an adapter between a ‘function’ or ‘function object’ and
std::thread. In general, before considering multi-threaded execution, we would define a function as
However, if the above function is used as the first argument of the
std::thread construct, the return value of the function will be ignored by
std::thread. In addition, if an exception is thrown to the above function,
std::thread will simply terminate the entire program. To resolve the interface gap, the C++ standard library defines a template for the
std::packaged_task class. It defines a
get_future member function to return a
std::future object that can receive a return value. It also defines an
operator() member function to call the original function.
A simplified implementation of
std::packaged_task is as follows (for reference only, the actual implementation is more complex).
The following is how
std::packaged_task is used.
Without changing the
compute function, we wrap the
compute function in
std::packaged_task. After calling
task(3, 4), the return value can be obtained by calling
We can then add the thread.
The above code replaces the original
task(3, 4) with the code that creates the thread. Since
std::packaged_task is a non-copyable class, we must transfer the
std::packaged_task object to the
std::thread construct with
std::move(task). Next, we call
t.detach() to avoid
std::thread’s destructor calling
std::terminate. On the other hand, the construct of
std::thread will call
std::packaged_task::operator() and execute the
compute function for us at another thread. When
compute finishes, the main thread can receive the returned value via
Finally, I would like to introduce the
std::async function sample. When using
std::async, we must pass in a
function object with the arguments needed to call the function.
std::async will call the function we pass in at a certain point in time. The caller of
std::async can read the return value of the incoming function via the
There are two different execution strategies for
std::launch::async: creates an execution thread, executes the specified work and returns a
std::launch::deferred: Returns a
std::future<T>object directly and defers the specified work to the call point of
For example, the previous example of
std::packed_task could also be rewritten as
The above code’s
std::async creates a thread to execute
compute(3, 4). The main thread gets the return value of
compute(3, 4) from
std::async(std::launch::async, ...) works as follows (for reference only, the actual implementation is more complex).
On the other hand, the
std::launch::deferred execution policy does not create a new execution thread. It works by maintaining an additional state inside the
std::future<T> object. When the user calls
get member function will execute the incoming function. If no one calls
std::async(std::launch::deferred, ...) will not execute the incoming function.
For example, in
std::launch::deferred mode, the following code must print the
first line before the
second line. If you delete the
f.get() line, the whole program must not print the