Введение в асинхронное программирование на C++
Современные приложения все чаще требуют высокой производительности и отзывчивости. Асинхронное программирование в C++ — это мощный инструмент, который позволяет выполнять несколько задач одновременно, не блокируя основной поток выполнения. В этом руководстве мы разберем основы асинхронности на C++: от классических подходов до современных корутин (coroutines), добавленных в стандарт C++20.
Если вы новичок в C++, но уже знакомы с базовым синтаксисом, эта статья поможет вам понять, как заставить вашу программу делать несколько дел одновременно. Мы рассмотрим практические примеры, которые можно сразу применить в своих проектах.
Основные концепции и зачем это нужно
В традиционном синхронном программировании код выполняется строго последовательно: одна операция завершается, затем начинается следующая. Это просто, но неэффективно, если программа ожидает ввода-вывода (чтение файла, сетевой запрос) или выполняет тяжелые вычисления.
Асинхронное программирование решает эту проблему, позволяя:
- Не блокировать основной поток — интерфейс приложения остается отзывчивым.
- Ускорять выполнение — параллельная обработка данных на многоядерных процессорах.
- Эффективно использовать ресурсы — пока одна задача ждет данные, другая может выполняться.
В C++ существует несколько способов реализовать асинхронность: от низкоуровневых потоков (std::thread) до высокоуровневых абстракций (std::async, std::future и корутины).
1. Асинхронность с std::async и std::future
Самый простой способ запустить асинхронную задачу в C++ — использовать функцию std::async из заголовочного файла <future>. Она возвращает объект std::future, который хранит результат выполнения.
Базовый пример
#include <iostream>
#include <future>
#include <thread>
#include <chrono>
// Функция, которую будем выполнять асинхронно
int slow_calculation(int x) {
std::this_thread::sleep_for(std::chrono::seconds(2)); // Имитация долгой работы
return x * x;
}
int main() {
std::cout << "Запускаем асинхронную задачу..." << std::endl;
// Запускаем slow_calculation в отдельном потоке
std::future<int> result = std::async(std::launch::async, slow_calculation, 5);
std::cout << "Основной поток продолжает работу..." << std::endl;
// Получаем результат (блокирует поток, если задача еще не выполнена)
int value = result.get();
std::cout << "Результат: " << value << std::endl; // Вывод: 25
return 0;
}
В этом примере std::async запускает функцию в отдельном потоке. Пока она выполняется, основной поток выводит сообщение. Метод get() ожидает завершения задачи и возвращает результат.
Политики запуска
У std::async есть два режима:
std::launch::async— задача гарантированно выполняется в новом потоке.std::launch::deferred— задача выполняется лениво, только при вызовеget()илиwait()в том же потоке.
2. Promises и Futures: ручное управление
Если вам нужно более гибкое управление асинхронными задачами, используйте пару std::promise и std::future. promise позволяет установить значение или исключение в одном потоке, а future — получить его в другом.
Пример с promise
#include <iostream>
#include <future>
#include <thread>
void worker(std::promise<int> prom, int a, int b) {
try {
int result = a / b; // Может выбросить исключение при делении на ноль
prom.set_value(result);
} catch(...) {
prom.set_exception(std::current_exception());
}
}
int main() {
std::promise<int> prom;
std::future<int>> fut = prom.get_future();
std::thread t(worker, std::move(prom), 10, 0); // Передаем promise в поток
try {
int result = fut.get();
std::cout << "Результат: " << result << std::endl;
} catch(const std::exception& e) {
std::cout << "Исключение: " << e.what() << std::endl;
}
t.join();
return 0;
}
Этот подход особенно полезен, когда нужно передать результат из одного потока в другой или обработать исключения.
3. Корутины в C++20 (Coroutines)
C++20 представил корутины — новый мощный механизм асинхронного программирования. Корутины позволяют приостанавливать и возобновлять выполнение функции без блокировки потока. Они идеально подходят для написания асинхронного кода, который выглядит как синхронный.
Простейшая корутина
Для работы с корутинами нужен компилятор с поддержкой C++20 (например, GCC 11+, Clang 14+, MSVC 2022). Вот минимальный пример:
#include <iostream>
#include <coroutine>
#include <optional>
// Простой генератор чисел
struct Generator {
struct promise_type {
int current_value;
Generator get_return_object() {
return Generator{std::coroutine_handle<promise_type>::from_promise(*this)};
}
std::suspend_always initial_suspend() { return {}; }
std::suspend_always final_suspend() noexcept { return {}; }
std::suspend_always yield_value(int value) {
current_value = value;
return {};
}
void return_void() {}
void unhandled_exception() { std::exit(1); }
};
std::coroutine_handle<promise_type> handle;
Generator(std::coroutine_handle<promise_type> h) : handle(h) {}
~Generator() { if (handle) handle.destroy(); }
bool next() {
if (!handle.done()) {
handle.res