Futures with std::async
The std::thread
does not provide an easy way to return a
value from a thread. We could do it via references but this approach
unnecessarily shares data between multiple threads and looks a bit cumbersome.
Another approach would be to use a condition variable from the previous article. The condition variable is associated with a condition and synchronizes threads when the condition is fulfilled. In our case, the condition would be the end of a thread and the result would be synchronized between the main and the spawned thread. It looks like that this is a special type of condition. Using the condition variable for returning from a thread seems like a big overhead. It must be some easier way to do it.
Thankfully, the standard template library provides a mechanism to return from a thread without using condition variables. The solution is to use the futures.
Futures
Instead of creating a thread with a function and its arguments, we create a future representation of the result. In other words: if we have a function
we can create a thread.
But now, we can not get the result of the function. Instead,
std::async
creates a future representation of the result
The template argument of the future is the return type of the function. In our
case, the template argument of the future
is
int
, because complicated_computation
return type is int
.
We can get the result of the function by calling the .get()
member function of the future.
The .get()
blocks current thread until the
complicated_computation
returns the result.
Lets look at another example.
Temperature tomorrow
The story is simple. A couple, wife and husband, are going on a picnic tomorrow. The wife would like to know what will be the temperature of the weather. Therefore, she asks her husband to look it up.
Again, keep in mind that the persons represent different threads (tasks).
We need to include <future>
in order to use them. Here is
also one helper function, which pauses current thread for a given number of
milliseconds. The purpose of this function is to better understand the execution
of the code.
The next section of the code represents the actions of the husband.
It prints some information and eventually returns 40
.
The last part of the code is the main function.
The interesting parts of the code are construction of the
std::future
object by calling the
std::async
and acquisition of the temperature by calling the
.get()
member function of the future.
When we run the example, we might get the following result.
But this looks strange. The temperature()
started to run when
the .get()
member function of the answer
was called. Therefore, the execution is not asynchronous.
The readers might think: “Jaka, did you mislead us? The
std::async
doesn’t create a new thread!” Well, I did mislead
you a little. The compiler can choose whether to run the function in a new
thread or synchronously. But don’t despair. We can explicitly set our compiler
to use either one of the policies.
Std::launch policy
The std::async
has an optional first argument which
determines how the std::async
computes the value of the
future. We can choose between three values for the first argument.
Asynchronous
The first value, which enables asynchronous evaluation, is
std::launch::async
. This means that the computation
(of temperature()
) will always be in a new thread. Our
code would then look like
and the next figure describes the timeline of the program in this case.
Lazy
The second value is std::launch::deferred
which enables lazy
evaluation. This means that the computation will be deferred until the
.get()
member function of the future will be called. This
happened in our example above.
The advantage of this option is that the computation might never occur, if we
never call the .get()
member function. For this option, our
code would look like:
The next figure describes the timeline of the program in this case.
Default
If we don’t provide the optional argument, the compiler will use the default
value which is std::launch::async | std::launch::deferred
.
This means that either one of the upper two cases might happen. In the example
above, we didn’t provide the optional argument and the implementation chose
std::launch::deferred
.
Summary
We learned how to return a value from a thread with futures and
std::async
. std::async
has two options.
-
It can run a function in a separate thread.
-
Or it can defer the execution of the function until the
.get()
member function is called.
Links: