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

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

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

Начать курс

Введение в асинхронное программирование на C++

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

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

Основы асинхронности: std::async и std::future

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

Базовый пример с std::async

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

int длительная_операция() {
    std::this_thread::sleep_for(std::chrono::seconds(2));
    return 42;
}

int main() {
    std::cout << "Запускаем асинхронную задачу..." << std::endl;
    
    // Запуск асинхронной задачи
    std::future<int> результат = std::async(std::launch::async, длительная_операция);
    
    std::cout << "Основной поток продолжает работу..." << std::endl;
    
    // Получение результата (блокирует поток до готовности)
    int значение = результат.get();
    std::cout << "Результат: " << значение << std::endl;
    
    return 0;
}

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

Работа с несколькими задачами

Асинхронное программирование особенно эффективно при параллельном выполнении нескольких независимых задач:

#include <iostream>
#include <future>
#include <vector>

int вычислить_квадрат(int x) {
    return x * x;
}

int main() {
    std::vector<std::future<int>> задачи;
    
    // Запускаем несколько асинхронных задач
    for (int i = 1; i <= 5; ++i) {
        задачи.push_back(std::async(std::launch::async, вычислить_квадрат, i));
    }
    
    // Собираем результаты
    for (auto& задача : задачи) {
        std::cout << "Результат: " << задача.get() << std::endl;
    }
    
    return 0;
}

Продвинутые инструменты: std::promise и std::packaged_task

Для более гибкого управления асинхронными операциями в C++ предусмотрены std::promise и std::packaged_task. Эти инструменты позволяют передавать значения между потоками и оборачивать вызываемые объекты.

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

std::promise позволяет установить значение, которое впоследствии будет получено через соответствующий объект std::future:

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

void поток_производитель(std::promise<std::string> обещание) {
    try {
        std::string данные = "Важные данные из потока";
        // Устанавливаем значение
        обещание.set_value(данные);
    } catch(...) {
        // Обработка исключений
        обещание.set_exception(std::current_exception());
    }
}

int main() {
    std::promise<std::string> обещание;
    std::future<std::string> будущее = обещание.get_future();
    
    std::thread поток(поток_производитель, std::move(обещание));
    
    // Получаем результат (блокируется до готовности)
    std::cout << "Получено: " << будущее.get() << std::endl;
    
    поток.join();
    return 0;
}

std::packaged_task для обертки функций

std::packaged_task оборачивает любую вызываемую сущность и автоматически создает std::future для получения результата:

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

int умножить(int a, int b) {
    return a * b;
}

int main() {
    // Оборачиваем функцию
    std::packaged_task<int(int, int)> задача(умножить);
    std::future<int> результат = задача.get_future();
    
    // Запускаем в отдельном потоке
    std::thread поток(std::move(задача), 7, 8);
    
    std::cout << "7 * 8 = " << результат.get() << std::endl;
    
    поток.join();
    return 0;
}

Современный подход: корутины C++20

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

Базовая структура корутины

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

struct Генератор {
    struct promise_type {
        int текущее_значение;
        
        Генератор get_return_object() {
            return Генератор{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 значение) {
            текущее_значение = значение;
            return {};
        }
        
        void return_void() {}
        void unhandled_exception() { std::terminate(); }
    };
    
    std::coroutine_handle<promise_type> handle;
    
    int получить_значение() {
        return handle.promise().текущее_значение;
    }
    
    bool продолжить() {
        if (!handle.done()) {
            handle.resume();
            return true;
        }
        return false;
    }                

Блоги

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