Spec-Zone.ru › C++

std::async

Определён в заголовочном файле <future>
template< class F, class... Args >
std::future</* see below */> async( F&& f, Args&&... args );
(1) (начиная с C++11)
template< class F, class... Args >
std::future</* see below */> async( std::launch policy,
                                    F&& f, Args&&... args );
(2) (начиная с C++11)

Шаблон функции std::async асинхронно выполняет функцию f (потенциально в отдельном потоке, который может быть частью пула потоков) и возвращает std::future, в котором впоследствии будет храниться результат вызова этой функции.

1) Ведёт себя так, как если бы (2) был вызван с policy, равным std::launch::async | std::launch::deferred.
2) Вызывает функцию f с аргументами args согласно заданной политике запуска policy (см. ниже).

Возвращаемый тип std::async — std::future<V>, где V — это:

typenamestd::result_of<typenamestd::decay<F>::type(
typenamestd::decay<Args>::type...)>::type.

(до C++17)

std::invoke_result_t<std::decay_t<F>,std::decay_t<Args>...>.

(начиная с C++17)

Если выполнено любое из следующих условий, программа некорректна:

  • F не является MoveConstructible.
  • Любой тип из Args не является MoveConstructible.
  • INVOKE(decay-copy(std::forward<F>(f)),
           decay-copy(std::forward<Args>(args))...)
    не является допустимым выражением.
(до C++20)

Если любое из следующих условий false, программа некорректна:

  • std::is_constructible_v<std::decay_t<F>, F>
  • (std::is_constructible_v<std::decay_t<Args>, Args> && ...)
  • std::is_invocable_v<std::decay_t<F>, std::decay_t<Args>...>
(начиная с C++20)

Вызов std::async синхронизируется с вызовом f, а завершение f упорядочено перед переводом общего состояния в готовое состояние.

Параметры

f - Вызываемый объект для вызова
args - параметры, передаваемые в f
policy - значение битовой маски, отдельные биты которой задают допустимые способы выполнения

Возвращаемое значение

std::future, ссылающийся на общее состояние, созданное этим вызовом std::async.

Политики запуска

Асинхронный вызов

Если установлен флаг async, то есть (policy & std::launch::async) != 0, тогда std::async вызывает

INVOKE(decay-copy(std::forward<F>(f)),
decay-copy(std::forward<Args>(args))...)

(до C++23)

std::invoke(auto(std::forward<F>(f)),auto(std::forward<Args>(args))...)

(начиная с C++23)

так, как если бы вызов выполнялся в новом потоке исполнения, представленном объектом std::thread.

Вызовы decay-copy вычисляются в текущем потоке.

(до C++23)

Значения, полученные с помощью auto, материализуются в текущем потоке.

(начиная с C++23)

Если функция f возвращает значение или выбрасывает исключение, оно сохраняется в общем состоянии, доступном через std::future, который std::async возвращает вызывающей стороне.

Отложенный вызов

Если установлен флаг deferred (то есть (policy & std::launch::deferred) != 0), тогда std::async сохраняет

decay-copy(std::forward<F>(f)) и decay-copy(std::forward<Args>(args))... в общем состоянии.

(до C++23)

auto(std::forward<F>(f)) и auto(std::forward<Args>(args))... в общем состоянии.

(начиная с C++23)

Выполняется ленивое вычисление:

  • При первом вызове функции ожидания без ограничения по времени для std::future, который std::async возвратил вызывающей стороне, вычисляется INVOKE(std::move(g),std::move(xyz)) в потоке, вызвавшем функцию ожидания (это не обязательно поток, изначально вызвавший std::async), где
  • g — это сохранённое значение decay-copy(std::forward<F>(f)), а
  • xyz — это сохранённая копия decay-copy(std::forward<Args>(args))....
(до C++23)
  • g — это сохранённое значение auto(std::forward<F>(f)), а
  • xyz — это сохранённая копия auto(std::forward<Args>(args))....
(начиная с C++23)
  • Результат или исключение помещается в общее состояние, связанное с возвращённым std::future, и только после этого состояние становится готовым. Все последующие обращения к тому же std::future немедленно возвращают результат.

Другие политики

Если в policy не установлен ни std::launch::async, ни std::launch::deferred, ни какой-либо флаг политики, определённой реализацией, поведение не определено.

Выбор политики

Если установлено несколько флагов, выбранная политика определяется реализацией. Для политики по умолчанию (когда в policy установлены оба флага — std::launch::async и std::launch::deferred) стандарт рекомендует (но не требует) использовать доступный параллелизм и откладывать выполнение остальных задач.

Если выбрана политика std::launch::async,

  • вызов функции ожидания для асинхронного объекта результата, разделяющего общее состояние, созданное этим вызовом std::async, блокируется до завершения связанного потока, как если бы к нему присоединились, либо до истечения времени ожидания; и
  • завершение связанного потока синхронизируется с успешным возвратом первой функции, ожидающей общее состояние, или с возвратом последней функции, освобождающей общее состояние, в зависимости от того, что произойдёт раньше.

Исключения

Выбрасывает

  • std::bad_alloc, если не удаётся выделить память для внутренних структур данных, или
  • std::system_error с условием ошибки std::errc::resource_unavailable_try_again, если policy == std::launch::async и реализация не может запустить новый поток.
    • Если policy равен std::launch::async | std::launch::deferred или содержит дополнительные установленные биты, в этом случае будет выполнен отложенный вызов либо будет использована политика, определённая реализацией.

Примечания

Реализация может расширить поведение первой перегрузки std::async, включив дополнительные биты политики запуска (определяемые реализацией) в политику запуска по умолчанию.

Примерами политик запуска, определяемых реализацией, являются политика sync (немедленное выполнение в рамках вызова std::async) и политика task (аналогична std::async, но локальные переменные потока не очищаются).

Если std::future, полученный из std::async, не перемещён и не привязан к ссылке, деструктор std::future будет блокировать выполнение в конце полного выражения до завершения асинхронной операции, фактически делая синхронным код, подобный следующему:

std::async(std::launch::async, []{ f(); }); // temporary's dtor waits for f()
std::async(std::launch::async, []{ g(); }); // does not start until f() completes

Обратите внимание, что деструкторы std::future, полученных не посредством вызова std::async, никогда не блокируют выполнение.

Пример

#include <algorithm>
#include <future>
#include <iostream>
#include <mutex>
#include <numeric>
#include <string>
#include <vector>

std::mutex m;

struct X
{
    void foo(int i, const std::string& str)
    {
        std::lock_guard<std::mutex> lk(m);
        std::cout << str << ' ' << i << '\n';
    }
    
    void bar(const std::string& str)
    {
        std::lock_guard<std::mutex> lk(m);
        std::cout << str << '\n';
    }
    
    int operator()(int i)
    {
        std::lock_guard<std::mutex> lk(m);
        std::cout << i << '\n';
        return i + 10;
    }
};

template<typename RandomIt>
int parallel_sum(RandomIt beg, RandomIt end)
{
    auto len = end - beg;
    if (len < 1000)
        return std::accumulate(beg, end, 0);
    
    RandomIt mid = beg + len / 2;
    auto handle = std::async(std::launch::async,
                             parallel_sum<RandomIt>, mid, end);
    int sum = parallel_sum(beg, mid);
    return sum + handle.get();
}

int main()
{
    std::vector<int> v(10000, 1);
    std::cout << "The sum is " << parallel_sum(v.begin(), v.end()) << '\n';
    
    X x;
    // Calls (&x)->foo(42, "Hello") with default policy:
    // may print "Hello 42" concurrently or defer execution
    auto a1 = std::async(&X::foo, &x, 42, "Hello");
    // Calls x.bar("world!") with deferred policy
    // prints "world!" when a2.get() or a2.wait() is called
    auto a2 = std::async(std::launch::deferred, &X::bar, x, "world!");
    // Calls X()(43); with async policy
    // prints "43" concurrently
    auto a3 = std::async(std::launch::async, X(), 43);
    a2.wait();                     // prints "world!"
    std::cout << a3.get() << '\n'; // prints "53"
} // if a1 is not done at this point, destructor of a1 prints "Hello 42" here

Возможный вывод:

The sum is 10000
43
world!
53
Hello 42

Отчёты об ошибках

Следующие отчёты об ошибках, изменяющие поведение, были ретроактивно применены к ранее опубликованным стандартам C++.

DR Применено к Поведение согласно опубликованному стандарту Корректное поведение
LWG 2021 C++11 возвращаемый тип был некорректным, а категория значения
аргументов в отложенном случае — неясной
возвращаемый тип исправлен,
уточнено, что используются rvalue
LWG 2078 C++11 было неясно, может ли быть выброшено std::system_error,
если policy задаёт другие
политики запуска помимо std::launch::async
может быть выброшено только если
policy == std::launch::async
LWG 2100 C++11 функции ожидания с ограничением по времени не могли завершиться
по тайм-ауту при использовании политики std::launch::async
разрешено
LWG 2120 C++11 поведение было неясным, если не задана стандартная
политика или политика, определённая реализацией
в этом случае поведение
не определено
LWG 2186 C++11 было неясно, как обрабатываются возвращённое значение и
исключение, выброшенное при ленивом вычислении
они сохраняются
в общем состоянии
LWG 2752 C++11 std::async могла не выбрасывать std::bad_alloc, если
не удавалось выделить память для внутренних структур данных
выбрасывает
LWG 3476 C++20 (декейированные типы) F и типы аргументов
должны были напрямую поддерживать конструирование перемещением
эти требования удалены[1]
  1. Поддержка конструирования перемещением уже косвенно требуется std::is_constructible_v.

См. также

future
(C++11)
ожидает асинхронно установленного значения
(шаблон класса)
Документация C++ для библиотеки поддержки выполнения

© cppreference.com
Licensed under the Creative Commons Attribution-ShareAlike Unported License v3.0.
https://en.cppreference.com/cpp/thread/async

Spec-Zone.ru

Настройки Оффлайн Что нового Помощь О нас
Spec-Zone .ru
спецификации, руководства, описания, API