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

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

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

Начать курс

Асинхронное программирование в 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 << "Запуск                

Блоги

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