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

int complicated_computation(double, bool);

we can create a thread.

std::thread thread(complicated_computation, 2.5, true);

But now, we can not get the result of the function. Instead, std::async creates a future representation of the result

std::future<int> future = std::async(complicated_computation, 2.5, true);

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.

int result = future.get();

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).

#include <iostream>
#include <string>
#include <chrono>

#include <thread>
#include <future>

void make_break(int milisec)
{
    std::this_thread::sleep_for(std::chrono::milliseconds(milisec));
}

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.

int temperature()
{
    std::cout << "Husband: Hm, is the weather "
              << "forecast in the newspaper?\n" 
              << "         Eh, we don't "
              << "have a newspaper at home..." << std::endl;
    
    make_break(2);
    
    std::cout << "Husband: I will look it up on the internet!" << std::endl;
    
    make_break(2);
    
    std::cout << "Husband: Here it is, "
              << "it says tomorrow will be 40." << std::endl;
    
    return 40;
}

It prints some information and eventually returns 40.

The last part of the code is the main function.

int main()
{
    std::cout << "Wife:    Tomorrow, we are going on a picnic.\n" 
              << "         What will be the weather...\n" 
              << "         \"What will be the "
              << "temperature tomorrow?\"" << std::endl;
    
    std::future<int> answer = std::async(temperature);
    
    make_break(2);
    
    std::cout << "Wife:    I should pack for tomorrow." << std::endl;
    
    make_break(2);

    std::cout << "Wife:    Hopefully my husband can figure out the weather soon."
              << std::endl;
    
    int temp = answer.get();

    std::cout << "Wife:    Finally, tomorrow will be " << temp << "... Em...\n"
              << "         \"In which units is the answer?\"" 
              << std::endl;

    return 0;
}

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.

$ ./future 
Wife:    Tomorrow, we are going on a picnic.
         What will be the weather...
         "What will be the temperature tomorrow?"
Wife:    I should pack for tomorrow.
Wife:    Hopefully my husband can figure out the weather soon.
Husband: Hm, is the weather forecast in the newspaper?
         Eh, we don't have a newspaper at home...
Husband: I will look it up on the internet!
Husband: Here it is, it says tomorrow will be 40.
Wife:    Finally, tomorrow will be 40... Em...
         "In which units is the answer?"

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

 
std::future<int> answer = std::async(std::launch::async, temperature); 

and the next figure describes the timeline of the program in this case.

Future

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:

std::future<int> answer = std::async(std::launch::deferred, temperature); 

The next figure describes the timeline of the program in this case.

Future1

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: