многопоточностьconcurrency

std::future / std::promise / std::async

Асинхронный результат вычисления без ручной синхронизации через mutex и condition_variable. promise — сторона, которая пишет результат; future — сторона, которая его ждёт и читает; async — удобная обёртка, запускающая функцию и сразу возвращающая future.An asynchronous computation result without manual synchronization via mutex and condition_variable. promise is the side that writes the result; future is the side that waits for and reads it; async is a convenience wrapper that starts a function and immediately returns a future for it.

одноразовый канал результатаa single-use result channel предок senders/receivers из C++26ancestor of C++26's senders/receivers

Что этоWhat it is

std::promise<T> и std::future<T> — парный одноразовый канал передачи результата (или исключения) между потоками: promise.set_value() на стороне производителя делает результат доступным, future.get() на стороне потребителя блокируется до появления результата и возвращает его ровно один раз. std::async — функция, которая запускает вызываемый объект (возможно, в отдельном потоке) и сразу возвращает future на его будущий результат.std::promise<T> and std::future<T> form a paired, single-use channel for passing a result (or an exception) between threads: promise.set_value() on the producer side makes the result available, future.get() on the consumer side blocks until the result appears and returns it exactly once. std::async is a function that launches a callable (possibly on a separate thread) and immediately returns a future for its eventual result.

вручную через thread + mutex + condvarby hand, via thread + mutex + condvar
int result; bool done = false;
std::mutex m; std::condition_variable cv;
std::thread t([&]{
    result = compute();
    { std::lock_guard<std::mutex> lk(m); done = true; }
    cv.notify_one();
});
std::unique_lock<std::mutex> lk(m);
cv.wait(lk, [&]{ return done; });
t.join();
std::async
std::future<int> fut = std::async(std::launch::async, compute);
int result = fut.get();   // блокируется до готовности,// blocks until ready,
                            // синхронизация — внутри future// synchronization lives inside future

Зачем ввелиWhy it was introduced

future/promise/async закрывают частый шаблонный код поверх thread + mutex + condition_variable:future/promise/async eliminate a common piece of boilerplate built on top of thread + mutex + condition_variable:

01

Передача одного результата — частая задача с шаблонным решениемPassing a single result is a common task with a boilerplate solution

Комбинация mutex + condition_variable + флаг готовности для передачи одного значения из одного потока в другой повторялась в коде настолько часто, что заслуживала стандартного, готового к использованию инструмента.The combination of mutex + condition_variable + a readiness flag for passing a single value from one thread to another showed up in code often enough to deserve a standard, ready-to-use tool.

02

Передача исключений между потокамиPropagating exceptions across threads

Исключение, брошенное внутри потока, не может быть напрямую поймано вызывающим кодом в другом потоке — future.get() решает и эту задачу: если задача, связанная с promise, кинула исключение, оно ретранслируется в вызове get() на стороне потребителя.An exception thrown inside a thread can't be caught directly by calling code on another thread — future.get() solves this too: if the task tied to the promise threw, that exception is rethrown at the get() call on the consumer side.

03

Единая абстракция вне зависимости от способа исполненияOne abstraction regardless of the execution mechanism

std::async может выполнить функцию как в отдельном потоке (std::launch::async), так и отложенно, при первом обращении к future (std::launch::deferred) — вызывающий код работает с future одинаково в обоих случаях, не завязываясь на конкретный механизм.std::async can run a function either on a separate thread (std::launch::async) or lazily, on the first access to the future (std::launch::deferred) — calling code works with the future the same way in both cases, without depending on the specific mechanism.

Как это работает под капотомHow it works under the hood

Внутри promise/future лежит общее разделяемое состояние (shared state), доступ к которому синхронизирован — концептуально тем же mutex + condition_variable, которые разобраны в mutex-condvar.html и подробно — в cpp_concurrency.md. std::future/std::promise можно рассматривать как готовую, стандартизованную реализацию именно того паттерна «одноразовая передача результата», который иначе пришлось бы писать вручную.Underneath, promise/future share a common shared state, access to which is synchronized — conceptually the same mutex + condition_variable covered in mutex-condvar.html and in detail in cpp_concurrency.md. std::future/std::promise can be seen as a ready-made, standardized implementation of exactly that "single-use result handoff" pattern you'd otherwise have to write by hand.

#include <future>

int compute(int x) { return x * x; }

int main() {
    // async — самый простой способ получить future:// async — the simplest way to get a future:
    std::future<int> fut = std::async(std::launch::async, compute, 6);
    // ... другая работа, пока compute считается в фоне ...// ... other work while compute runs in the background ...
    int result = fut.get();     // блокируется, если ещё не готово; 36// blocks if not ready yet; 36

    // promise — явный контроль над тем, кто и когда пишет результат:// promise — explicit control over who writes the result and when:
    std::promise<int> prom;
    std::future<int> fut2 = prom.get_future();
    std::thread t([&prom] { prom.set_value(compute(7)); });
    std::cout << fut2.get();    // 49
    t.join();
}

future.get() можно вызвать только один раз — после этого future переходит в невалидное состояние; для повторного чтения результата несколькими потребителями в C++11 нет прямого решения (std::shared_future появляется тоже в C++11 именно для этого случая — множественные получатели одного результата).future.get() can only be called once — after that the future moves into an invalid state; C++11 has no direct solution for reading the result repeatedly from multiple consumers (std::shared_future, also introduced in C++11, exists precisely for that case — multiple recipients of the same result).

ГраблиGotchas

std::async без std::launch::async может не создать поток вообщеstd::async without std::launch::async might not create a thread at all

Вызов std::async(compute, x) без явной policy оставляет выбор между async и deferred на усмотрение реализации — если реализация выберет deferred, функция выполнится синхронно внутри вызова fut.get(), а не в фоне. Для гарантированного параллельного исполнения нужно явно указывать std::launch::async.Calling std::async(compute, x) without an explicit policy leaves the choice between async and deferred up to the implementation — if the implementation picks deferred, the function runs synchronously inside the fut.get() call rather than in the background. Guaranteed concurrent execution requires explicitly specifying std::launch::async.

Деструктор future от std::async может блокироватьсяA future's destructor from std::async can block

В отличие от future, полученного из promise напрямую, future от std::async в некоторых реализациях блокируется в своём деструкторе до завершения задачи, если get()/wait() не были вызваны явно — неожиданная задержка в месте, которое выглядит как простое уничтожение объекта.Unlike a future obtained directly from a promise, a future from std::async, in some implementations, blocks in its destructor until the task finishes if get()/wait() weren't called explicitly — an unexpected delay at a spot that looks like a plain object destruction.

Повторный get() на одном future — undefined behaviorCalling get() twice on the same future is undefined behavior

std::future рассчитан на одноразовое получение результата — второй вызов get() на том же объекте не определён стандартом (обычно кидает std::future_error). Для множественных читателей нужен std::shared_future, который допускает многократный get().std::future is designed for a single retrieval of the result — a second get() call on the same object is not defined by the standard (it typically throws std::future_error). Multiple readers need std::shared_future, which allows calling get() repeatedly.