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

Инструменты сайта


cpp:corutines

C++ Corutines

Тезисы, как я это понял

cppreference.com - Coroutines (C++20)

Coroutine - "это магия компилятора".

1. Компилятор считает корутиной функцию:

  • в которой используются операторы co_yield / co_await / co_return
  • и которая возвращает структуру с именем promise_type, содержащую набор необходимых компилятору функций, которые он вставит в "нужные места" обработки корутины:
    • get_return_object()
    • initial_suspend()
    • final_suspend()
    • return_void() {}
    • unhandled_exception()

"нужные места" - это примерно так:

{
  co_await promise.initial_suspend();
  try
  {
    // код корутины
  }
  catch (...)
  {
    promise.unhandled_exception();
  }
FinalSuspend:
  co_await promise.final_suspend();
}

CASE1: Определение типа promise_type через using:

struct promise;
 
struct coroutine : std::coroutine_handle<promise>
{
    using promise_type = ::promise;
};

struct promise
{
    coroutine get_return_object() { return {coroutine::from_promise(*this)}; }
    std::suspend_always initial_suspend() noexcept { return {}; }
    std::suspend_always final_suspend() noexcept { return {}; }
    void return_void() {}
    void unhandled_exception() {}
};

// Использование
void good()
{
    // h - это корутина, потому что содержит co_return 
    // и возвращает структуру coroutine, содержащую promise_type 
    coroutine h = [](int i) -> coroutine // make i a coroutine parameter
    {
        std::cout << i;
        co_return;
    }(0);
    // lambda destroyed
    h.resume(); // no problem, i has been copied to the coroutine
                // frame as a by-value parameter
    h.destroy();
}

CASE2: Здесь компилятора сам найдет promise_type в возвращаемой структуре task:

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

// resuming_on_new_thread - это корутина, потому что содержит co_await 
// и возвращает структуру task, содержащую promise_type 
task resuming_on_new_thread(std::jthread& out)
{
    std::cout << "Coroutine started on thread: " << std::this_thread::get_id() << '\n';
    co_await switch_to_new_thread(out);
    // awaiter destroyed here
    std::cout << "Coroutine resumed on thread: " << std::this_thread::get_id() << '\n';
}

2. Действия при запуске корутины

В месте вызова корутины, перед выполнением самого кода корутины, компилятор закладывает выполнение следующих шагов:

  • вычисление размера фрейма корутины и аллокация памяти под него в куче (оператор new).
  • копирование в фрейм всех значений переменных и параметров использующихся корутиной.
  • вызов конструктора объекта promise типа promise_type и сохранение указателя в фрейме.
  • вызов promise.get_return_object() для получения return-object. В return-object корутина вернет значение при первом suspend или при завершении тела. Важно получить return-object до исполнения тела корутины, иначе при окончании корутины фрейм и promise будут уже разрушены и вызвать get_return_object() не получится.
  • Чтобы после окончания корутины, через эту переменную можно было узнать результат выполнения, возвращаемый корутиной.
  • вызов co_await promise.initial_suspend(), чтобы
    • std::suspend_always: сразу вернуться в коллер, не доходя до тела цикла. Тогда продолжение исполнения корутины возобновится при следующем вызове корутины.
    • std::suspend_never: либо перейти в обработке тела корутины.
  • исполнение тела корутины

2. Действия при завершении корутины

При достижении co_return:

  • вызов promise.return_void() или promise.return_value(<expr>)
  • уничтожение всех переменных с автоматическим временем жизни в порядке обратном созданию
  • вызов co_await promise.final_suspend(). Выход сразу или при следующем вызове SuspendNewer/SuspendAlways.

При возникновении исключения:

  • вызов promise.unhandled_exception() в catch-block.
  • вызов co_await promise.final_suspend()

При окончании тела корутины:

  • вызов деструктора promise
  • вызов деструкторов копий параметров функций
  • освобождение памяти занятой фреймом корутины (оператор delete).
  • возврат к коллеру.
cpp/corutines.txt · Последнее изменение: 2024/08/21 13:28 — vasco