Simple threading with std::async

By , last updated August 18, 2019

Threading with C++11 comes in two flavours, std::thread and std::async. Though similar, they are different. While std::thread gives you a thread to work with, std::async may or may not execute a certain task on a different thread.

Notice the usage of “may or may not”. This is a feature of std::async.

Warning: This post is about std::async and simple threading and tasks. This means std::async is not suited for long lived tasks requiring I/O (input/output), hold mutexes and be a part of a thread pool. For such uses have a look at boost::asio or similar libraries.

First example with std::async

The std::async residesĀ in the header <future>. Include it with the #include directive.

#include <future>

This will allow us to access most functionality related to std::async. If you want to print anything to the console window, you must include the <iostream> header.

#include <iostream>

Now on to the example itself. It probably isn’t easier than this to run methods on other threads with C++. First we create a method, which only prints the current thread id.

void printThreadId() {
    std::cout << "Current thread id: " << std::this_thread::get_id() << "\n";
}

This method will print something like this. The only difference will be the thread id, as it’s a value from the operating system and not something the language C++ or the runtime has any control over.

Current thread id: 43576

Next up is the main method. With it, we’ll be able to run the program. The method printThreadId(); will print the main thread id. After that, the line std::async(&printThreadId); will launch printThreadId on a different thread. That is, it may or may not run on a different thread than the main thread.

The complete main method with output.

int main()
{
    // Print main thread id
    std::cout << "Main thread id: ";
    printThreadId();
    
    // Launch printThreadId on a different thread
    std::async(&printThreadId);
    std::cout << "After async dispatch.\n";
    
    return 0;
}

Even this example has one not-very-obvious caution (caveat). If the std::async runs on an other thread, the async call will not return until printThreadId is done working. That is, std::async will block. Not very intuitive at first, but there is a perfectly logical reason behind this.

Read also: How to stop a std:async task

The output should be something like this, proving std::async is synchronous.

Main thread id: Current thread id: 38228
Current thread id: 21684
After async dispatch.

Async std::async

If you want to use the asynchronous features of std::async, you must save the result from std::async call in a variable. It will return something called a future (std::future). You can think of a future as a variable, which will give you a value in the future. A future is a promise. A future will wait for the promise to be fulfilled, and the future will wait until the promise if an other thread is calling future.get() or future.wait().

This is the case with calling std::async and not saving the return value (future). The future is destroyed before returning, with the effect that std::async is a synchronous call (in which it may or may not run on a different thread). According to the standard (section 30.6.4 5.3) this is the only time destruction of a future will block.

Modify our example by saving the return value from std::async.

<b>auto future = </b>std::async(&printThreadId);
std::cout << "After async dispatch.\n";
future.wait();
std::cout << "After future.wait\n";

The output (with my implementation, MSVC 2015) states that the std::async is executed between the std::cout calls. This means std::async does not block execution and execution is possibly deferred until future.wait is called.

Main thread id: Current thread id: 6304
After async dispatch.
Current thread id: 18552
After future.wait

Async std::async with simulated processing

A modern CPU will not break any sweat printing a couple of characters to the screen. To simulate processing time, we can use std::this_thread::sleep. Using sleep for std::async tasks do have some issues, namely the runtime is free to decouple the thread and execution of the thread if it finds it necessary.

To simulate a heavy simulation we’ll use the new standard chrono and thread library to sleep for a given time. This will be a template helper ensuring only std::chrono::duration are allowed as a parameter. Don’t worry too much about the implementation. It’s use will become apparent soon.

template<typename REP, typename DUR>
void wait(std::chrono::duration<REP, DUR> & dur) {
    std::this_thread::sleep_for(dur);
}

To use the chrono library, the <chrono> header must be included.

#include <chrono>

The C++11 standard also introduces something called string literals. This allows a programmer to create types based on the suffix of a number or string. This is very useful together with the chrono library. The chrono library defines a couple of durations as string literal suffices. The usage is straight forward. With using namespace std::literals, you’ll drag the literals into your namespace. Actually using namespace std::literals is one of very few instances where I would recommend using the using namespace construct.

using namespace std::literals;

auto ms = 1ms;
auto second = 1s;
auto minute = 1min;
auto hour = 1h;

The alternative to using namespace std::literals is not pretty, except when using std::chrono::milliseconds and the family of common durations.

// Using string literals without using namespace std::literals
auto ms = std::literals::operator""ms(1ULL);

// Using durations in std::chrono
auto ms = std::chrono::milliseconds(1);

To find out what really happens behind the curtain of my implementation of std::async, I’m simulating a job taking 0.5 seconds, and it will take 1 second for the caller to get to future.wait(). With use of print statements, we’ll be able to pinpoint when the job is executing.

Add this method (printThreadIdWait). It will 0.5 seconds before printing anything. Notice the use of 500ms to wait 500 milliseconds.

void printThreadId() {
    wait(500ms);
    std::cout << "Current thread id: " << std::this_thread::get_id() << "\n";
}

And then add some lines to the main method.

auto future = std::async(&printThreadIdWait);
wait(1000ms);
std::cout << "After async dispatch.\n";
future.wait();
std::cout << "After future.wait\n";
void printThreadIdWait() {
    wait(500ms);
    std::cout << "Wait thread id: " << std::this_thread::get_id() << "\n";
}

The output is proves the async task is run in parallel with the main thread.

Main thread id: Current thread id: 7272
Wait thread id: 336
After async dispatch.
After future.wait

Professional Software Developer, doing mostly C++. Connect with Kent on Twitter.