Асинхронное программирование в C++: полное руководство с примерами

Онлайн-тренажер Python для начинающих

Изучайте Python легко и без перегрузки теорией. Решайте практические задачи с автоматической проверкой, получайте подсказки на русском языке и пишите код прямо в браузере — без необходимости что-либо устанавливать.

Начать курс

Асинхронное программирование в C++: от std::async до корутин

Асинхронное программирование в C++ — это мощный инструмент для создания высокопроизводительных и отзывчивых приложений. В современном мире, где многоядерные процессоры стали стандартом, умение эффективно управлять параллельными задачами — обязательный навык для каждого C++ разработчика. В этой статье мы разберем ключевые концепции, начиная с базовых механизмов стандартной библиотеки и заканчивая современными корутинами из C++20.

Зачем нужно асинхронное программирование?

Основная цель асинхронного программирования — не блокировать основной поток выполнения программы. Представьте, что ваше приложение должно загрузить файл из сети, обработать его и показать пользователю. Если выполнять эти операции синхронно, интерфейс "зависнет" на время загрузки. Асинхронный подход позволяет запустить длительную операцию "в фоне", а основной поток продолжает обрабатывать пользовательский ввод.

Основные подходы к асинхронности в C++

В C++ существует несколько способов реализовать асинхронное выполнение:

  • Ручное управление потоками (std::thread) — низкоуровневый, но гибкий подход.
  • std::async и std::future — высокоуровневый механизм для запуска асинхронных задач.
  • std::packaged_task и std::promise — для более тонкого контроля.
  • Корутины (C++20) — современный и элегантный способ написания асинхронного кода.

Использование std::async и std::future

Это самый простой способ запустить асинхронную задачу. Функция std::async возвращает объект std::future, который будет содержать результат выполнения.

#include <iostream>
#include <future>
#include <thread>
#include <chrono>

int heavy_computation(int x) {
    std::this_thread::sleep_for(std::chrono::seconds(2));
    return x * x;
}

int main() {
    std::cout << "Запускаем асинхронную задачу...\n";
    std::future<int> result = std::async(std::launch::async, heavy_computation, 42);
    
    // Делаем что-то ещё, пока задача выполняется
    std::cout << "Основной поток не заблокирован!\n";
    
    // Получаем результат (блокируется, если задача ещё не завершена)
    int value = result.get();
    std::cout << "Результат: " << value << "\n";
    return 0;
}

Флаг std::launch::async гарантирует, что задача будет выполнена в отдельном потоке. Если его не указать, реализация может отложить выполнение до вызова .get().

Работа с std::promise и std::packaged_task

Эти механизмы предоставляют больше контроля. std::promise позволяет "обещать" значение, которое будет установлено позже, а std::packaged_task оборачивает вызываемый объект в задачу.

#include <iostream>
#include <future>
#include <thread>

void set_value(std::promise<int>&& prom) {
    std::this_thread::sleep_for(std::chrono::seconds(1));
    prom.set_value(100);
}

int main() {
    std::promise<int> prom;
    std::future<int> fut = prom.get_future();
    
    std::thread t(set_value, std::move(prom));
    
    std::cout << "Ожидаем результат...\n";
    int value = fut.get();
    std::cout << "Получено: " << value << "\n";
    
    t.join();
    return 0;
}

Важно: std::promise и std::future — это move-only объекты. Их нельзя копировать, только перемещать.

Корутины в C++20: новый уровень асинхронности

Корутины (coroutines) — это функции, которые могут приостанавливать своё выполнение и возобновлять его позже. Они позволяют писать асинхронный код так, как будто он синхронный.

Базовый пример корутины

#include <iostream>
#include <coroutine>
#include <thread>
#include <chrono>

struct SimpleTask {
    struct promise_type {
        SimpleTask 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(); }
    };
};

SimpleTask async_hello() {
    std::cout << "Привет из корутины! Начинаем ожидание...\n";
    co_await std::suspend_always{};
    std::cout << "Продолжаем после приостановки!\n";
}

int main() {
    async_hello();
    std::this_thread::sleep_for(std::chrono::seconds(1));
    std::cout << "Основной поток завершён.\n";
    return 0;
}

Ключевое слово co_await приостанавливает выполнение корутины до тех пор, пока не будет выполнено некоторое условие (например, завершится ввод-вывод или пройдет таймер).

Практический пример: асинхронная загрузка данных

#include <iostream>
#include <future>
#include <coroutine>
#include <exception>

// Простая обёртка для std::future как awaitable
template<typename T>
struct FutureAwaiter {
    std::future<T> future;
    
    bool await_ready() { return future.wait_for(std::chrono::seconds(0)) == std::future_status::ready; }
    void await_suspend(std::coroutine_handle<> handle) {
        std::thread([this, handle]() {
            future.wait();
            handle.resume();
        }).detach();
    }
    T await_resume() { return future.get(); }
};

template<typename T>
FutureAwaiter<T> make_awaitable(std::future<T> future) {
    return {std::move(future)};
}

// Корутина
std::future<int> async_coroutine() {
    auto result = co_await make_awaitable(std::async(std::launch::async, []() {
        std::this_thread::sleep_for(std::chrono::seconds(2));
        return                

Блоги

Рекомендаций по книгам