Вызов std::async выполнен без указания политики запуска. Какую гарантию о параллельном выполнении даёт такой код?
#include <chrono>
#include <future>
#include <thread>
int work() {
std::this_thread::sleep_for(std::chrono::milliseconds{10});
return 42;
}
int main() {
auto result = std::async(work);
result.wait();
}
Код не гарантирует запуск work в отдельном потоке и параллельное выполнение. При вызове std::async без политики запуска реализация может выбрать std::launch::async или std::launch::deferred.
При выборе deferred функция выполнится отложенно — в потоке, который впервые вызовет подходящую операцию ожидания, здесь это result.wait(). Поэтому std::async(work) без явной политики нельзя использовать как гарантию создания рабочего потока.
std::async появился в C++11 как высокоуровневый способ запустить вычисление и получить его результат через std::future. Подход должен был скрыть детали передачи результата и ожидания завершения операции.
Политика по умолчанию допускает как немедленный асинхронный запуск, так и отложенное выполнение. Это оставляет реализации возможность выбирать вариант с учётом ресурсов, но делает гарантии параллелизма слабее, чем при явном указании std::launch::async.
Если разработчик рассчитывает, что work уже выполняется одновременно с текущим потоком, а реализация выбрала deferred, ожидаемого перекрытия вычислений не будет. Вызов wait() фактически может сам выполнить функцию и только после этого завершиться.
Неверное предположение особенно опасно для таймаутов, распараллеливания независимых задач и кода, который ожидает прогресса другого потока. Программа может оставаться корректной с точки зрения результата, но потерять производительность или нарушить требования к времени выполнения.
У перегрузки std::async без политики запуска используется набор политик, эквивалентный std::launch::async | std::launch::deferred. Реализация выбирает одну из них для конкретного вызова.
При std::launch::async вызываемая функция запускается асинхронно, а future представляет состояние, в котором появится результат. При std::launch::deferred функция не запускается сразу; она выполняется при первом вызове операции ожидания, способной запустить отложенное вычисление, например get() или wait().
Политику можно проверить через операцию с таймаутом:
wait_for при статусе deferred не запускает функцию, а сообщает, что вычисление отложено. Если нужен именно отдельный поток, следует явно написать std::launch::async:
Это устраняет неопределённость выбора политики, но не означает, что создание неограниченного числа таких задач всегда эффективно. Для большого количества коротких операций обычно требуется контролируемый пул потоков или другой механизм планирования.
Сервис одновременно формирует два независимых отчёта и использует два вызова std::async без политики запуска. На тестовой машине отчёты иногда выполняются параллельно, а иногда последовательно, из-за чего задержка ответа нестабильна.
Вариант с политикой по умолчанию прост и позволяет реализации выбирать режим, но не даёт гарантии параллелизма. Явный std::launch::async обеспечивает запуск каждой задачи в асинхронном режиме, однако может создать слишком много потоков при большом числе запросов.
Для небольшого фиксированного числа тяжёлых отчётов выбран std::launch::async: требование параллельного выполнения важнее стоимости создания потоков. Если число задач возрастает, более устойчивым решением становится пул потоков с ограниченным количеством рабочих потоков.
wait_for?Нет. При политике deferred wait_for возвращает std::future_status::deferred, но не выполняет функцию. Для запуска отложенной задачи нужно вызвать get() или wait() либо другую подходящую операцию ожидания без таймаута.
std::launch::async физически новый поток на каждый вызов?Гарантируется асинхронное выполнение функции, отличное от непосредственного выполнения в вызывающем потоке, но полагаться на конкретную модель управления потоками как на способ построения масштабируемого пула не следует. При большом числе вызовов явный async может привести к существенным затратам на потоки и планирование.
Надёжно — нет. Быстрое выполнение не доказывает наличие deferred, а медленное не доказывает наличие отдельного потока: на результат влияют планировщик, загрузка системы и сама функция. Для различения режимов используется проверка результата wait_for на std::future_status::deferred.