Корутины

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

В отличие от системных потоков, которые переключаются системой в произвольные моменты времени (вытесняющая многозадачность), корутины переключаются вручную в местах, указанных программистом (кооперативная многозадачность).

Простыми словами: корутина может остановиться и передать управление другому потоку (другой корутине), а потом вернуться к текущей инструкции и продолжить выполнение до либо очередной паузы и передачи потока выполнения, либо завершения работы.

В C++20 есть три механизма для работы с корутинами:

co_await - ожидание асинхронного результата co_yield - приостанавливает работу корутины и возвращает какое-то значение co_return - возвращает значение и завершает работу корутины

Функция является корутиной, если в ней есть хотя бы одна из этих трех команд.

У корутин в С++ есть ограничения:

  • Обязана иметь тип возрата (?)
  • Не может быть функцией с variadic templates
  • Не может быть функцией с оператором return
  • Не может быть функцией с автоматичским выведением типа возвращаемого значения (auto)
  • Не может быть constexpr-функцией
  • Не может быть конструктором
  • Не может быть деструктором
  • Не может быть функцией main

Есть два типа корутин.

Stackless-корутины

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

Аргументы по значению она скопирует/помувает, а ссылки останутся ссылками (отсюда следует, что когда корутина вернулась к выполнению кода, у нее может остаться невалидная ссылка, если объект уже уничтожили).

Всю магию переключений между stackless-корутинами компилятор вправе реализовать через конечный автомат и скорее всего так и сделает (в интернете есть пример с огромным оператором switch).

Stackfull-корутины

Такие корутины имеют свой собственный стек и менеджатся хорошо только на уровне ОС. Иначе можно делать магию через свап в фреймах оперативной памяти с помощью ассемблерного кода - что-то об этом упоминал Иван Сорокин на лекциях по C++ Advanced в университете ИТМО.

Stackfull-корутины появились в WinApi уже давно и называются Fiber.

Примеры использования корутины

X coroutine() {
    co_yield "Hello ";
    co_yield "world";
    co_return "!";
}

int main() {
    auto x = coroutine();
    std::cout << x.next();
    std::cout << x.next();
    std::cout << x.next();
    std::cout << std::endl;
}

Кажется, что тип X нужно писать самому.

X foo() {
    co_return 42;
}

X bar() {
    const auto result = foo();
    const int i = co_await result;
    co_return i + 23;
}

Пример корутины-генератора для факториала:

X factorial() {
    int a = 1;
    int b = 1;
    for (;;) {
        b *= a;
        a += 1;
        co_yield b;
    }
}

Заимствования:

c++ - Сопрограммы (корутины, coroutine) - что это? - Stack Overflow на русском