Your country

Tools that support it use your country for local currency, number formats, units and paper size. Your choice is saved only in this browser.

Type a name or a two-letter code. Use the up and down arrow keys to move through the countries, Enter to choose one and Escape to close.

C++  Module 16 – Concurrency: threads, atomics and the memory model

std::async, futures and promises

Run tasks with std::async and collect their results through futures, get exceptions back from other threads, and fulfil futures by hand with promises.

  • Intermediate
  • 30 minutes
  • Examples run with Clang 21.0.0, GCC 16.2.0 and Python 3.14.8
  • By MySmartCoPilot
Free core, Pro depth: 11 of 30 minutes free
Free: std::async starts each task at once, and get() waits for its result · Exceptions come back through get() · The launch policy decides where and when the task runs · 2 more sections. With a pass: 3 in-depth parts and 3 interview answers, about 19 minutes.

What you will learn

  • Run tasks asynchronously with std::async and collect their results with std::future
  • Catch a task's exception in the thread that reads its result
  • Choose a launch policy and predict where and when a task runs
  • Complete a future by hand with std::promise and std::packaged_task
  • Avoid the blocking destructor of the futures that std::async returns

Before you start

Four downloads of 300, 200, 400 and 100 ms take 1.0 s one after the other and 0.4 s when they run at once, the time of the slowest. std::async runs a function as a task, usually on another thread, and returns a std::future: The reading end of a result that another thread or a deferred task produces: get() waits until the result is ready, then returns the value or throws the stored exception. A std::future can be read once.: a handle to a result that does not exist yet. Calling get() on the future waits until the task has finished and then returns its value, or throws the exception: An object thrown with throw to report that a function cannot do what it was asked. Control leaves the function at once and passes up the chain of callers to the first handler whose type matches the object. More in “C++ exceptions: throw, catch and stack unwinding” the task threw. Futures are how one thread hands a single result to another without sharing variables or locks.

When a result comes from code you run yourself instead of from std::async, promises do the writing: a std::promise: The writing end of a shared state that your own code fulfils, once, with set_value() or set_exception(); get_future() gives the std::future that reads it. is the end you fulfil, and its future is the end you read, while std::packaged_task turns any function into a task with a future. This lesson runs four simulated downloads at once, sends an exception back through a future, shows where each launch policy: The first argument of std::async: std::launch::async runs the task as if on a new thread, at once; std::launch::deferred runs it on the caller, inside the first get() or wait(); without one, the library chooses. runs a task, and fulfils a future by hand.

std::async starts each task at once, and get() waits for its result

Four “downloads” that only sleep, so the example needs no network, run first one after the other and then all at once with std::async:

downloads.cpp C++ · downloads.cpp
// Four "downloads" that only sleep, so the example needs no network: first one after the other, then all at once with
// std::async. Each returns the size of the file it pretends to fetch.
#include <chrono>
#include <functional>
#include <future>
#include <print>
#include <string>
#include <thread>
#include <vector>

using namespace std::chrono_literals;
using Clock = std::chrono::steady_clock;

struct File {
    std::string name;
    std::chrono::milliseconds takes;
    long bytes;
};

long download(const File& f) {
    std::this_thread::sleep_for(f.takes);  // stands in for waiting on the network
    return f.bytes;
}

double seconds_since(Clock::time_point start) { return std::chrono::duration<double>(Clock::now() - start).count(); }

int main() {
    const std::vector<File> files{{"a.csv", 300ms, 1200}, {"b.csv", 200ms, 3400}, {"c.csv", 400ms, 560}, {"d.csv", 100ms, 7800}};

    auto start = Clock::now();
    long total = 0;
    for (const File& f : files) total += download(f);
    std::println("one after the other: {} bytes in {:.1f} s", total, seconds_since(start));

    start = Clock::now();
    std::vector<std::future<long>> pending;
    for (const File& f : files) pending.push_back(std::async(std::launch::async, download, std::cref(f)));
    total = 0;
    for (std::future<long>& p : pending) total += p.get();  // waits for each result in turn
    std::println("with std::async:     {} bytes in {:.1f} s", total, seconds_since(start));
}
One after the other, then all at once Python · downloads_session.py
from session import run

# downloads.cpp built and run with Apple clang.
run("""
clang++ -std=c++23 -O2 -Wall -Wextra downloads.cpp -o downloads && ./downloads
""")

Output

$ clang++ -std=c++23 -O2 -Wall -Wextra downloads.cpp -o downloads && ./downloads
one after the other: 12960 bytes in 1.0 s
with std::async:     12960 bytes in 0.4 s

This output changes from run to run: the times change slightly from run to run

Recorded with Python 3.14.8 on macOS 26 arm64. To run it yourself: mise exec python@3.14.8 -- python3 downloads_session.py

One after the other, the four took the sum of their times, about a second. With std::async they ran at the same time, so the whole job took as long as the slowest download, 0.4 s. Each call returned a std::future<long>, kept in a vector, and the loop over get() collected the results in order: a get() on a finished task returns at once, and a get() on a running one waits for it. Here is that loop with its four files and their times:

Interactive · message by message Four downloads at once, collected in order Step through the std::async loop of downloads.cpp. Times are from the start of the loop; main reads the four futures in order a, b, c, d.
Four downloads at once, collected in order, step 8 of 8. Messages so far: main to tasks a and b: std::async × 2; main to tasks c and d: std::async × 2; tasks a and b to main: a: 1,200 bytes; tasks a and b to main: b: 3,400, at once; tasks c and d to main: c: 560 bytes; tasks c and d to main: d: 7,800, at once.main12,960 bytes in 0.4 stasks a and bb ready: 3,400 bytestasks c and dd ready: 7,800 bytesstd::async × 2std::async × 2a: 1,200 bytesb: 3,400, at oncec: 560 bytesd: 7,800, at once

The drawing shows the whole scene; its 8 steps are listed below.

Every step as text
  1. Two tasks start. At 0 ms, main calls std::async(std::launch::async, download, …) for a.csv and b.csv. Each call starts its task on another thread at once and returns a std::future<long> that main keeps.
  2. Two more start. c.csv and d.csv start the same way. All four tasks now run at the same time, and main has four futures in a vector.
  3. get() on a.csv waits. main calls get() on the future of a.csv. The task is still running, so get() waits.
  4. d and b finish first. d.csv finishes at 100 ms and b.csv at 200 ms. Each task writes its result into its future, which is now ready, while main is still waiting for a.
  5. a arrives. a.csv finishes at 300 ms, and get() returns its 1,200 bytes.
  6. b was ready. get() on b.csv returns at once: that task finished 100 ms ago, and its future kept the 3,400 bytes.
  7. c, the slowest. get() on c.csv waits until 400 ms, when the slowest download finishes with 560 bytes.
  8. d, at once. get() on d.csv returns its 7,800 bytes at once. The total is 12,960 bytes after 0.4 s, the time of the slowest download, where one after the other took 1.0 s.

Futures make the hand-over safe: the task writes its result once, and the standard guarantees that get() sees it complete, with no mutex in sight.

Checkpoint

Check: Starting tasks and reading their futures

  1. Question 1 of 2 Four tasks that take 300, 200, 400 and 100 ms are started at once with std::async(std::launch::async, …), and main then calls get() on each future. About how many milliseconds after the start does the last get() return?

    Type a number.

    A hint

    The tasks run at the same time, so the slowest one decides.

    Show the answer: Question 1 of 2

    Answer: 400 ms (anything from 380 to 420 counts)

    All four run at the same time, so collecting them takes as long as the slowest, 400 ms. downloads.cpp printed 0.4 s for the same four times, against 1.0 s one after the other.

  2. Question 2 of 2 In downloads.cpp, main calls get() on the futures of a.csv (300 ms), b.csv (200 ms), c.csv (400 ms) and d.csv (100 ms), in that order. Which of those calls return at once, without waiting?

    Choose every answer that is right.

    Show the answer: Question 2 of 2

    Answer:

    • get() on b.csv
    • get() on d.csv

    A get() on a finished task returns at once, and a get() on a running one waits for it. Reading in the order a, b, c, d, main waits for a and c, and finds b and d already done.

Exceptions come back through get()

A task can fail, and its exception must not be lost on another thread. std::async catches whatever the task throws, stores it in the future, and get() throws it again in the thread that asks for the result:

exceptions.cpp C++ · exceptions.cpp
// A task that throws: the future stores the exception, and get() throws it again in the thread that asks.
#include <future>
#include <print>
#include <stdexcept>
#include <string>

int parse_port(const std::string& text) {
    const int port = std::stoi(text);  // throws std::invalid_argument when text is not a number
    if (port < 1 || port > 65535) throw std::out_of_range("port " + text + " is not between 1 and 65535");
    return port;
}

int main() {
    for (std::string text : {"8080", "eighty", "70000"}) {
        std::future<int> port = std::async(std::launch::async, parse_port, text);
        try {
            std::println("{:>6}: port {}", text, port.get());
        } catch (const std::invalid_argument&) {
            std::println("{:>6}: get() threw the task's std::invalid_argument", text);
        } catch (const std::out_of_range& e) {
            std::println("{:>6}: get() threw std::out_of_range: {}", text, e.what());
        }
    }
}
Three tasks, two of them failing, both compilers Python · exceptions_session.py
from session import run

# exceptions.cpp with both compilers.
run("""
clang++ -std=c++23 -Wall -Wextra exceptions.cpp -o exceptions && ./exceptions
g++-16 -std=c++23 -Wall -Wextra exceptions.cpp -o exceptions-gcc && ./exceptions-gcc
""")

Output

$ clang++ -std=c++23 -Wall -Wextra exceptions.cpp -o exceptions && ./exceptions
  8080: port 8080
eighty: get() threw the task's std::invalid_argument
 70000: get() threw std::out_of_range: port 70000 is not between 1 and 65535
$ g++-16 -std=c++23 -Wall -Wextra exceptions.cpp -o exceptions-gcc && ./exceptions-gcc
  8080: port 8080
eighty: get() threw the task's std::invalid_argument
 70000: get() threw std::out_of_range: port 70000 is not between 1 and 65535

Recorded with Python 3.14.8 on macOS 26 arm64. To run it yourself: mise exec python@3.14.8 -- python3 exceptions_session.py

The trip of the second task’s exception takes five steps, from std::stoi on one thread to the catch that handles it in main:

Interactive · message by message An exception thrown on one thread, caught on another Step through the task for "eighty". The middle column is the future's shared state, which holds a value or an exception.
An exception thrown on one thread, caught on another, step 5 of 5. Messages so far: main to the task: parse_port("eighty"); the task to shared state: store the exception; shared state to main: rethrow invalid_argument.maincaught in mainshared stateready: an exceptionthe taskthrows invalid_argumentparse_port("eighty")store the exceptionrethrow invalid_argument

The drawing shows the whole scene; its 5 steps are listed below.

Every step as text
  1. The task starts. main calls std::async(std::launch::async, parse_port, text) with text "eighty", and the task starts on another thread.
  2. The task throws. std::stoi cannot read a number from "eighty" and throws std::invalid_argument. Nothing in the task catches it.
  3. std::async catches it. std::async catches whatever the task throws and stores it in the future's shared state, which is now ready, holding an exception instead of a value.
  4. get() throws it again. main's get() finds the shared state ready and throws the stored exception again, in main, with its type intact.
  5. An ordinary catch. The catch for std::invalid_argument in main handles it, and the program prints "eighty: get() threw the task's std::invalid_argument". The out_of_range of "70000" travels the same way, with its message.

The std::invalid_argument that std::stoi threw on another thread reached main with its type intact, so an ordinary catch handled it, and so did the std::out_of_range with its message. That is a real difference from a std::thread, where an exception that leaves the thread’s function ends the whole program with std::terminate: The function C++ calls when exception handling cannot go on, for example when no handler matches a throw or an exception tries to leave a noexcept function. By default it calls std::abort, which ends the program with SIGABRT. More in “C++ exceptions: throw, catch and stack unwinding”.

Checkpoint

Check: Exceptions that come back through get()

  1. Question 1 of 2 A task started with std::async throws std::out_of_range("port 70000 is not between 1 and 65535"). What does catch (const std::out_of_range& e) around get() in main see?

    Choose one answer.

    Show the answer: Question 1 of 2

    Answer: The same exception, with its type and its message, rethrown by get()

    std::async catches what the task throws and stores it in the future's shared state; get() throws that same exception again in the thread that asks for the result.

  2. Question 2 of 2 The same parse_port("eighty") runs as the function of a std::thread instead of a std::async task, with nothing catching inside it. What happens?

    Choose one answer.

    Show the answer: Question 2 of 2

    Answer: The exception leaves the thread's function and the whole program ends with std::terminate()

    That is the difference the lesson points out: with std::async the exception reaches main through get(), with a std::thread it ends the whole program.

The launch policy decides where and when the task runs

std::async takes an optional first argument, its launch policy, that says how to run the task:

  • std::launch::async runs it as if on a new thread, at once.
  • std::launch::deferred runs nothing yet: the task runs inside the first get() or wait(), on the calling thread.
  • No policy means either of the two, and the library chooses.
policies.cpp C++ · policies.cpp
// The three ways to call std::async, and where and when each one runs its task.
#include <chrono>
#include <format>
#include <future>
#include <print>
#include <string>
#include <thread>

using namespace std::chrono_literals;

int main() {
    const std::thread::id main_id = std::this_thread::get_id();
    auto where = [main_id](std::string policy) {
        return std::format("{}: ran on {}", policy, std::this_thread::get_id() == main_id ? "the main thread" : "another thread");
    };
    std::future<std::string> a = std::async(std::launch::async, where, "launch::async");
    std::future<std::string> d = std::async(std::launch::deferred, where, "launch::deferred");
    std::future<std::string> b = std::async(where, "no policy (async | deferred)");

    std::println("the deferred task has not started yet: {}", d.wait_for(0s) == std::future_status::deferred);
    std::println("{}", a.get());
    std::println("{}", d.get());  // the deferred task runs now, inside get(), on this thread
    std::println("{}", b.get());
}
Three policies, both compilers Python · policies_session.py
from session import run

# policies.cpp with both compilers.
run("""
clang++ -std=c++23 -Wall -Wextra policies.cpp -o policies && ./policies
g++-16 -std=c++23 -Wall -Wextra policies.cpp -o policies-gcc && ./policies-gcc
""")

Output

$ clang++ -std=c++23 -Wall -Wextra policies.cpp -o policies && ./policies
the deferred task has not started yet: true
launch::async: ran on another thread
launch::deferred: ran on the main thread
no policy (async | deferred): ran on another thread
$ g++-16 -std=c++23 -Wall -Wextra policies.cpp -o policies-gcc && ./policies-gcc
the deferred task has not started yet: true
launch::async: ran on another thread
launch::deferred: ran on the main thread
no policy (async | deferred): ran on another thread

Recorded with Python 3.14.8 on macOS 26 arm64. To run it yourself: mise exec python@3.14.8 -- python3 policies_session.py

Both libraries started a thread when no policy was given, but the standard allows either choice, so code that needs the task to run concurrently should say std::launch::async. A deferred task is a lazily computed value: it is useful when the result may never be needed, and it is a trap when the code assumes it runs in the background.

Three ways to call std::async, as policies.cpp ran them
Criterion std::launch::asyncstd::launch::deferredNo policy
Where the task runs As if on a new threadOn the thread that calls get() or wait()Either, as the library chooses
When it starts At onceInside the first get() or wait()Either
wait_for(0s) before get() ready or timeoutdeferredDepends on the choice
In this lesson's runs Another thread, both librariesThe main thread, both librariesAnother thread, both libraries
When to choose A task that must run alongside the callerA value that may never be neededCode that does not care where or when

Checkpoint

Check: Launch policies

  1. Question 1 of 2 auto d = std::async(std::launch::deferred, f); Which thread runs f, and when?

    Choose one answer.

    Show the answer: Question 1 of 2

    Answer: The thread that first calls d.get() or d.wait(), inside that call

    A deferred task is a lazily computed value: it is stored in the future and runs on the calling thread, inside the first get() or wait(). Until then it does not run at all.

  2. Question 2 of 2 Code needs its task to run concurrently with the caller on every standard library. What should it pass to std::async?

    Choose one answer.

    Show the answer: Question 2 of 2

    Answer: std::launch::async

    std::async(f) without a policy means async | deferred, and the implementation decides. Code that relies on the task running in the background should say std::launch::async.

Promises and packaged tasks fill futures by hand

std::async decides how the task runs. When you already have a thread, or the result appears in a callback, promises let you fulfil a future yourself:

promise.cpp C++ · promise.cpp
// A std::promise is the writing end of a future: a thread you start yourself fulfils it, and the future reads it.
#include <chrono>
#include <future>
#include <print>
#include <string>
#include <thread>
#include <utility>

using namespace std::chrono_literals;

int main() {
    std::promise<std::string> greeting;
    std::future<std::string> result = greeting.get_future();  // the reading end, taken before the promise moves away

    std::jthread worker([p = std::move(greeting)]() mutable {
        std::this_thread::sleep_for(100ms);  // some work
        p.set_value("hello from the worker");
    });

    std::println("main is waiting");
    std::println("main got: {}", result.get());  // blocks until set_value() has run
}
A promise fulfilled by a worker thread Python · promise_session.py
from session import run

# promise.cpp built and run with Apple clang under ThreadSanitizer.
run("""
clang++ -std=c++23 -g -O1 -fsanitize=thread -Wall -Wextra promise.cpp -o promise && ./promise
""")

Output

$ clang++ -std=c++23 -g -O1 -fsanitize=thread -Wall -Wextra promise.cpp -o promise && ./promise
main is waiting
main got: hello from the worker

Recorded with Python 3.14.8 on macOS 26 arm64. To run it yourself: mise exec python@3.14.8 -- python3 promise_session.py

Step through the hand-over in promise.cpp: who holds which end, and when the value moves:

Interactive · message by message A promise fulfilled by a worker, read through its future Step through promise.cpp. main holds the reading end, the worker the writing end; the middle column is the shared state they share.
A promise fulfilled by a worker, read through its future, step 5 of 5. Messages so far: main to shared state: get_future(); main to worker: std::move(greeting); worker to shared state: set_value(...); shared state to main: "hello from the worker".maingot the stringshared stateready: the stringworkerowns the promiseget_future()std::move(greeting)set_value(...)"hello from the worker"

The drawing shows the whole scene; its 5 steps are listed below.

Every step as text
  1. Take the reading end first. main creates std::promise<std::string> greeting and calls get_future() before anything else, while it still holds the promise.
  2. Hand over the writing end. The promise is moved into the worker's lambda, so the worker now owns the writing end. A promise cannot be copied, only moved.
  3. main waits in get(). main prints "main is waiting" and calls result.get(). The shared state is not ready yet, so get() blocks.
  4. The worker fulfils it. After 100 ms of work, the worker calls set_value("hello from the worker"). The value goes into the shared state, which becomes ready, once and for good.
  5. get() returns the value. get() returns the string, complete, with no mutex in sight: the standard guarantees that the reader sees what the writer stored. main prints "main got: hello from the worker", and ThreadSanitizer reported nothing.

A task, promise or packaged_task stores a result in a shared state; std::future reads it once, std::shared_future many times.the writing end, used onceshared statea value or an exception,and whether it is readythe reading endstd::async's taskreturns or throwsstd::promiseset_value()set_exception()std::packaged_taskruns a functionstd::futureget() oncestd::shared_futureget() any number of timesstores the result,then marks it readyget() waits until ready,then returns or throws

One shared state, a writing end and a reading end

Text description of the diagram

The diagram reads from top to bottom.

  • At the top is the writing end, used once. It is one of three things: the task of std::async, which returns or throws; a std::promise, through set_value() or set_exception(); or a std::packaged_task, which runs a function.
  • An arrow labelled "stores the result, then marks it ready" leads to the shared state in the middle, which holds a value or an exception and whether it is ready.
  • An arrow labelled "get() waits until ready, then returns or throws" leads to the reading end at the bottom: a std::future, whose get() can be called once, or a std::shared_future, whose get() can be called any number of times.

All the writing ends meet the reading ends in a shared state: The object that a future shares with its writing end, a std::async task, a std::promise or a std::packaged_task: it holds one value or one exception, and whether it is ready., which holds one value or one exception and a “ready” flag. std::promise<T> fulfils it with set_value() or set_exception(), exactly once. std::packaged_task<R()> wraps a function so that calling the task runs the function and fulfils the future, which is how a queue of jobs hands each submitter its own result. On the reading side, a std::future can be read once, and a std::shared_future any number of times, by any number of threads. The Core Guidelines recommend this style for returning results from concurrent work (CP.60, CP.61).

Checkpoint

Check: Promises, packaged tasks and shared futures

  1. Question 1 of 2 In promise.cpp, main calls result.get() before the worker has called set_value(). What does get() do?

    Choose one answer.

    Show the answer: Question 1 of 2

    Answer: It waits until set_value() has stored the value, then returns it

    get() blocks until the shared state is ready, here when the worker's set_value() runs, and then returns the value, complete, with no mutex needed.

  2. Question 2 of 2 Three threads all need the result of one task. What should they read it through?

    Choose one answer.

    Show the answer: Question 2 of 2

    Answer: A std::shared_future, whose get() any number of threads may call

    On the reading side, a std::future can be read once and a std::shared_future any number of times. Calling share() on a future turns it into a shared_future.

Practise: tasks, futures, policies and promises

Each of the eleven questions below is about std::async, get(), a launch policy or a promise as the four sections above explain them, and the idea with the fewest right answers so far goes first.

Practice that adapts to you

Practise this lesson

11 questions about 7 ideas of this lesson. Each answer is explained at once, and what you get right is kept in this browser.

  1. Question 1 of 11 What does std::async(std::launch::async, download, std::cref(f)) return?

    Choose one answer.

    Show the answer: Question 1 of 11

    Answer: A std::future<long>, a handle to the result download will produce

    std::async starts the task and returns a std::future at once. get() on that future waits for the task and returns its value, or throws its exception.

  2. Question 2 of 11 A loop calls std::async(std::launch::async, download, std::cref(f)).get() for each of the four files, on one line. How long does it take, about?

    Choose one answer.

    Show the answer: Question 2 of 11

    Answer: About 1.0 s, as long as one after the other, because each get() waits before the next task starts

    Concurrency comes from starting all the tasks before reading any result. downloads.cpp kept the futures in a vector and read them afterwards, which is why it took as long as the slowest download.

  3. Question 3 of 11 The four download tasks write their results while main reads them, with no mutex anywhere. Why is that safe?

    Choose one answer.

    Show the answer: Question 3 of 11

    Answer: Each task writes its result once into its own future, and the standard guarantees that get() sees it complete

    Futures are how one thread hands a single result to another without sharing variables or locks: the writing end stores it once, and get() on the reading end returns it complete.

  4. Question 4 of 11 A task started with std::async throws std::invalid_argument. What happens?

    Choose one answer.

    Show the answer: Question 4 of 11

    Answer: The future stores the exception, and get() throws it again in the thread that calls get()

    std::async catches whatever the task throws and stores it in the future's shared state; get() then throws that same exception, with its own type, so a catch for std::invalid_argument in the caller works, as exceptions.cpp showed with both compilers.

  5. Question 5 of 11 exceptions.cpp parses "8080", "eighty" and "70000". Which line did it print for "70000"?

    Choose one answer.

    Show the answer: Question 5 of 11

    Answer: 70000: get() threw std::out_of_range: port 70000 is not between 1 and 65535

    std::stoi reads 70000 without trouble, and the range check throws std::out_of_range with its message. The catch for out_of_range in main printed e.what(), the task's own message.

  6. Question 6 of 11 When does a task started with std::async(std::launch::deferred, f) run?

    Choose one answer.

    Show the answer: Question 6 of 11

    Answer: When get() or wait() is first called on its future, on the thread that calls it

    A deferred task is only stored; it runs lazily, inside the first get() or wait(), on the calling thread, which is why policies.cpp reported that it ran on the main thread. Until then wait_for() reports std::future_status::deferred.

  7. Question 7 of 11 What does the standard say about std::async(f) called without a launch policy?

    Choose one answer.

    Show the answer: Question 7 of 11

    Answer: It means std::launch::async | std::launch::deferred, and the implementation chooses which one applies

    Without a policy the library may run the task on a new thread or defer it. libc++ and libstdc++ both started a thread in this lesson's run, but code that needs the task to run concurrently should say std::launch::async.

  8. Question 8 of 11 A value is expensive to compute and is needed only on one rare branch, on the same thread. Which launch policy fits?

    Choose one answer.

    Show the answer: Question 8 of 11

    Answer: std::launch::deferred, so the work runs only if that branch calls get()

    Deferred is lazy evaluation, not concurrency: useful when the result may never be needed, and a trap when the code assumes it runs in the background.

  9. Question 9 of 11 What is a std::promise for?

    Choose one answer.

    Show the answer: Question 9 of 11

    Answer: Fulfilling a future by hand, from code you run yourself, with set_value() or set_exception()

    A promise is the writing end of a shared state and get_future() gives its reading end. Any thread may fulfil it once, with a value or an exception. std::shared_future is what makes a result readable more than once.

  10. Question 10 of 11 A queue of jobs must hand each submitter its own result. Which tool wraps a function so that running it fulfils a future?

    Choose one answer.

    Show the answer: Question 10 of 11

    Answer: std::packaged_task, whose call runs the function and fulfils its future

    std::packaged_task<R()> wraps a function so that calling the task runs the function and fulfils the future, the building block of job queues and thread pools.

  11. Question 11 of 11 What does the shared state between a writing end and a reading end hold?

    Choose one answer.

    Show the answer: Question 11 of 11

    Answer: One value or one exception, and whether it is ready

    All the writing ends, a std::async task, a std::promise or a std::packaged_task, meet the reading ends in a shared state that holds one value or one exception and a ready flag.

Three future traps, reproduced: blocking destructors, deferred polls, a second get() Pro: opens with a Pro, Premium or Ultimate pass

Futures are simple until they are destroyed, polled or read twice. This part reproduces one trap of each kind, times the first, and shows what libc++ and libstdc++ do when the rules are broken.

In this part:

  • The future that waits in its destructor, timed
  • Polling a deferred future never ends
  • A second get() is undefined, and the libraries disagree

1 diagram · 4 runnable examples (C++, Python) · about 6 minutes

Opening this part with your pass…

This part opens with any MySmartCoPilot pass.

  • Pro pass · 1 week₹79$2.99
  • 30 days₹179$5.99
  • 1 year₹1,699$45

One payment; a pass never renews. The price shown is the total you pay: no fees or charges are added at checkout.

It also opens every other in-depth part in Learn and every Pro tool. Sales are final, except where the law gives you a right to withdraw, for example in the EU and the UK (refund policy).

Promises and packaged tasks by hand, with their failure cases Pro: opens with a Pro, Premium or Ultimate pass

A promise must be fulfilled exactly once, and every way of breaking that rule has its own error. This part triggers each one, then builds a worker that hands out results of any type through packaged tasks.

In this part:

  • Four ways a promise goes wrong
  • A job queue with packaged tasks
  • Turning a callback into a future

3 runnable examples (C++, Python) · about 4 minutes

Opening this part with your pass…

This part opens with any MySmartCoPilot pass.

  • Pro pass · 1 week₹79$2.99
  • 30 days₹179$5.99
  • 1 year₹1,699$45

One payment; a pass never renews. The price shown is the total you pay: no fees or charges are added at checkout.

It also opens every other in-depth part in Learn and every Pro tool. Sales are final, except where the law gives you a right to withdraw, for example in the EU and the UK (refund policy).

When std::async is the wrong tool, measured, with an exercise Pro: opens with a Pro, Premium or Ultimate pass

Every std::async call with the async policy starts a thread, and starting a thread costs tens of microseconds or more. This part measures what that does to 10,000 small jobs on both libraries, then asks you to use std::async where it fits: a handful of slow, independent tasks.

In this part:

  • 10,000 small jobs, three ways, measured
  • Your turn: word counts from slow files

4 runnable examples (C++, Python) · about 4 minutes

Opening this part with your pass…

This part opens with any MySmartCoPilot pass.

  • Pro pass · 1 week₹79$2.99
  • 30 days₹179$5.99
  • 1 year₹1,699$45

One payment; a pass never renews. The price shown is the total you pay: no fees or charges are added at checkout.

It also opens every other in-depth part in Learn and every Pro tool. Sales are final, except where the law gives you a right to withdraw, for example in the EU and the UK (refund policy).

Interview questions

  1. Fresher to mid · Warm-up

    What is the difference between std::launch::async and std::launch::deferred, and what does std::async do without a policy?

    std::launch::async runs the task as if on a new thread, starting at once, so it really runs at the same time as the caller. std::launch::deferred runs nothing at first: the task is stored in the future and runs on the calling thread inside the first get() or wait(), which is lazy evaluation, not concurrency; wait_for() on such a future returns std::future_status::deferred. Without a policy, std::async may do either, and the implementation chooses. libc++ and libstdc++ both start a thread, but portable code that needs the task to run concurrently should pass std::launch::async. Whatever the policy, get() returns the task's value or throws its exception.

  2. Mid to senior

    A loop calls std::async(std::launch::async, upload, file) for 20 files without keeping the returned futures. It takes as long as uploading the files one by one. Explain why, and fix it.

    Model answer, Pro. Model answers open with any pass; prices are in the notice on this page. First step and follow-ups

  3. Mid to senior

    A worker thread fulfils a std::promise with its result. In production the consumer sometimes blocks in get() for ever, and sometimes gets std::future_error with broken_promise. What went wrong in each case, and how do you make sure the consumer always receives a value or an error?

    Model answer, Pro. First step and follow-ups

  4. Mid to senior

    You must run 10,000 independent tasks of about 50 microseconds each and combine their results. Would you start each one with std::async? Defend your answer with numbers.

    Model answer, Pro. First step and follow-ups

Key takeaways

  • std::async runs a function as a task and returns a std::future; get() waits and returns the value or throws the task’s exception.
  • Say std::launch::async when the task must run concurrently; a deferred task runs inside the first get().
  • A std::promise fulfils a future from code you run yourself; std::packaged_task turns a function into a task with a future.
  • A std::future can be read once; use std::shared_future when several readers need the result.

Terms in this lesson

launch policy
The first argument of std::async: std::launch::async runs the task as if on a new thread, at once; std::launch::deferred runs it on the caller, inside the first get() or wait(); without one, the library chooses.
shared state
The object that a future shares with its writing end, a std::async task, a std::promise or a std::packaged_task: it holds one value or one exception, and whether it is ready.
std::future
The reading end of a result that another thread or a deferred task produces: get() waits until the result is ready, then returns the value or throws the stored exception. A std::future can be read once.
std::promise
The writing end of a shared state that your own code fulfils, once, with set_value() or set_exception(); get_future() gives the std::future that reads it.

References

Report a problem with this lesson

Quick answers and tool search

Type to search tools or to get a quick answer, for example 18% of 2500. Use the up and down arrow keys to move through the results, Enter to choose, and Escape to close.