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

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

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

Начать курс

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

Современные приложения требуют высокой производительности и отзывчивости. Асинхронное программирование в C++ позволяет выполнять длительные операции (ввод-вывод, сетевые запросы, сложные вычисления) без блокировки основного потока. В этом руководстве мы разберем ключевые механизмы C++ для асинхронной работы: от классических std::thread и std::async до современных корутин C++20. Вы узнаете, как писать эффективный и безопасный асинхронный код.

1. Основы: std::thread и std::async

Начнем с базовых инструментов. std::thread создает новый поток выполнения, но требует ручного управления и синхронизации. Более удобный вариант — std::async, который запускает функцию асинхронно и возвращает std::future для получения результата.

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

int long_running_task(int input) {
    std::this_thread::sleep_for(std::chrono::seconds(2));
    return input * 10;
}

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

Ключевые моменты:

  • std::launch::async — гарантирует запуск в отдельном потоке.
  • std::launch::deferred — откладывает выполнение до вызова get().
  • Метод get() блокирует поток до получения результата.

2. Продвинутая работа: std::promise и std::future

Когда нужно передать результат из одного потока в другой, используйте пару std::promise и std::future. promise устанавливает значение, а future его получает. Это особенно полезно при пулинге потоков или цепочках задач.

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

void worker(std::promise<int> prom) {
    try {
        int result = 42; // Сложные вычисления
        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));
    
    // Получаем результат
    int value = fut.get();
    std::cout << "Получено значение: " << value << std::endl;
    
    t.join();
    return 0;
}

Важные особенности:

  • promise нельзя копировать, только перемещать.
  • Исключения передаются через set_exception.
  • future можно использовать только один раз (однократное получение).

3. Корутины C++20: современный подход

Корутины (coroutines) — мощный инструмент, добавленный в C++20. Они позволяют приостанавливать и возобновлять выполнение функции, не блокируя поток. Это идеально для асинхронных операций ввода-вывода и генераторов.

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

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::exit(1); }
    };
};

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

int main() {
    auto task = async_operation();
    std::cout << "Основной поток\n";
    return 0;
}

Более практичный пример с использованием std::future:

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

class AsyncFuture {
public:
    struct promise_type {
        AsyncFuture get_return_object() { return AsyncFuture{this}; }
        std::suspend_never initial_suspend() { return {}; }
        std::suspend_always final_suspend() noexcept { return {}; }
        void return_void() {}
        void unhandled_exception() { std::terminate(); }
    };

    AsyncFuture(promise_type* p) : promise_(p) {}
    ~AsyncFuture() { if (handle_) handle_.destroy(); }

private:
    std::coroutine_handle<promise_type> handle_;
};

AsyncFuture async_task() {
    std::cout << "Корутина: начало\n";
    co_await std::suspend_always{};
    std::cout << "Корутина: продолжение\n";
}

int main() {
    auto task = async_task();
    std::cout << "Главный поток\n";
    return 0;
}

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

4. Пул потоков и параллельные алгоритмы

Для массовых параллельных вычислений используйте std::execution (C++17) и пулы потоков. Это позволяет легко распараллелить алгоритмы STL.

#include <iostream>
#include <vector>
#include <algorithm>
#include <execution>
#include <numeric>

int main() {
    std::vector<int> data(1000000);
    std::iota(data.begin(), data.end(), 1);
    
    // Параллельное суммирование
    long long sum = std::reduce(std::execution::par, data.begin(), data.end(), 0LL);
    std::cout << "Сумма: " << sum << std::endl;
    
    // Параллельная сортировка
    std::sort(std::execution::par, data.begin(), data                

Блоги

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