Асинхронное программирование в C++: полное руководство на практике
Асинхронное программирование в C++ — это подход, позволяющий выполнять несколько задач параллельно, не блокируя основной поток выполнения. В отличие от синхронного кода, где каждая операция ожидает завершения предыдущей, асинхронный код может инициировать длительные операции (ввод-вывод, сетевые запросы, вычисления) и продолжать выполнение других задач, пока эти операции завершаются в фоне. Это критически важно для создания отзывчивых приложений, особенно в игровой индустрии, веб-серверах и системах реального времени.
В данной статье мы разберем асинхронное программирование в C++ на практике: от классических механизмов std::async и std::future до современных корутин C++20. Вы узнаете, как правильно использовать эти инструменты, избегать типичных ошибок и создавать эффективные асинхронные системы.
1. Основы асинхронности: std::async и std::future
Стандартная библиотека C++11 предоставляет простой способ запуска асинхронных задач — функцию std::async и класс std::future. std::async запускает функцию в отдельном потоке (или пуле потоков) и возвращает std::future, который содержит результат выполнения.
Пример 1: Базовый запуск асинхронной задачи
#include <iostream>
#include <future>
#include <thread>
#include <chrono>
int longComputation(int n) {
std::this_thread::sleep_for(std::chrono::seconds(2));
return n * n;
}
int main() {
std::cout << "Запуск асинхронной задачи..." << std::endl;
std::future<int> result = std::async(std::launch::async, longComputation, 10);
// Пока задача выполняется, можно делать другую работу
std::cout << "Основной поток продолжает работу..." << std::endl;
std::this_thread::sleep_for(std::chrono::seconds(1));
// Получение результата (блокирует поток, если задача еще не завершена)
int value = result.get();
std::cout << "Результат: " << value << std::endl;
return 0;
}
Важные моменты:
std::launch::asyncгарантирует запуск в отдельном потоке.result.get()блокирует текущий поток до получения результата.- Если не вызвать
get(), деструкторstd::futureможет заблокировать выполнение до завершения задачи.
Пример 2: Обработка исключений в асинхронных задачах
#include <iostream>
#include <future>
#include <stdexcept>
int riskyFunction(int x) {
if (x < 0) throw std::runtime_error("Отрицательное число!");
return x * 2;
}
int main() {
std::future<int> future = std::async(std::launch::async, riskyFunction, -5);
try {
int result = future.get();
std::cout << "Результат: " << result << std::endl;
} catch (const std::exception& e) {
std::cout << "Исключение: " << e.what() << std::endl;
}
return 0;
}
2. Продвинутая работа с std::future и std::promise
std::promise — это инструмент для ручного управления асинхронными результатами. Вы можете установить значение или исключение в одном потоке, а получить его через std::future в другом. Это полезно для реализации собственных асинхронных паттернов, например, для передачи данных между потоками.
Пример 3: Использование std::promise для передачи данных
#include <iostream>
#include <future>
#include <thread>
void producer(std::promise<std::string> prom) {
std::this_thread::sleep_for(std::chrono::seconds(1));
prom.set_value("Данные готовы!");
}
int main() {
std::promise<std::string> promise;
std::future<std::string> future = promise.get_future();
std::thread t(producer, std::move(promise));
t.detach();
std::cout << "Ожидание данных..." << std::endl;
std::string data = future.get();
std::cout << "Получено: " << data << std::endl;
return 0;
}
Совет: Всегда проверяйте, что std::promise не уничтожен до установки значения. Иначе std::future выбросит исключение std::future_error.
3. Асинхронные корутины в C++20
Стандарт C++20 ввел корутины — мощный механизм для написания асинхронного кода, который выглядит как синхронный. Корутины позволяют приостанавливать выполнение функции в точке co_await и возобновлять его позже, не блокируя поток. Это делает код более читаемым и эффективным по сравнению с ручным управлением потоками.
Пример 4: Простая корутина с co_await
#include <iostream>
#include <coroutine>
#include <thread>
#include <chrono>
struct Awaiter {
std::chrono::seconds duration;
bool await_ready() { return false; }
void await_suspend(std::coroutine_handle<> handle) {
std::thread([handle, this]() {
std::this_thread::sleep_for(duration);
handle.resume();
}).detach();
}
void await_resume() {}
};
struct Task {
struct promise_type {
Task get_return_object() { return {}; }
std::suspend_never initial_suspend() { return {}; }
std::suspend_never final_suspend() noexcept { return {}; }
void return_void() {}
void unhandled_exception() { std::terminate(); }
};
};
Task asyncFunction() {
std::cout << "Начало корутины" << std::endl;
co_await Awaiter{std::chrono::seconds(2)};
std::cout << "Возобновление после ожидания" << std::endl;
}
int main() {
std::cout << "Запуск