Корутины — приостановленные функции, а не «асинхронные»
До C++20 у разработчика был грубый выбор: написать асинхронный код через колбэки (callback hell, инвертированный поток управления) или через потоки (дорого, нужна синхронизация). Корутины дают третий путь — функция, которая умеет приостановиться в произвольной точке и возобновиться позже, не занимая системный поток пока она ждёт.
Ключевая идея — не «асинхронная функция», а suspendable function. Корутина сама по себе не запускается в фоне; её состояние лежит в куче в виде coroutine frame, а кто и когда позовёт resume() — определяется внешним планировщиком, генератором или I/O-событием. Асинхронность, генераторы, lazy-последовательности и кооперативная многозадачность — это всё разные сценарии поверх одного механизма suspension.
C++20 даёт только «кости» механизма: ключевые слова, типы (coroutine_handle, suspend_always/never), требования к promise_type и Awaitable. Сам тип корутины (Task<T>, Generator<T>, Lazy<T>…) пишет пользователь или библиотека (cppcoro, Asio, std::execution). Полная карта по слоям — ниже.
Карта темы
- Что такое корутина — функция с точками приостановки, lazy-семантика, отличие от обычной функции.
- co_yield, co_await, co_return — три ключевых слова и что они означают семантически.
- Coroutine frame — где живёт состояние, heap-аллокация, HALO-оптимизация, частые ловушки.
- std::coroutine_handle — resume / destroy / done,
from_promise, владение и время жизни. - promise_type — точки настройки:
get_return_object,initial_suspend,final_suspend,return_value,unhandled_exception. - Awaitable-протокол — три метода (
await_ready/await_suspend/await_resume) и какco_awaitразворачивается в них. - Генераторы —
co_yield-последовательности,std::generator<T>из C++23, ленивые ranges. - Symmetric transfer — возврат
coroutine_handle<>изawait_suspend, tail-call resume и почему это спасает стек.
Частые ошибки и ловушки
| Ошибка | Последствие |
|---|---|
| Захватили ссылку на параметр вызывающей стороны и продолжаем после suspend | Dangling reference во frame — UB после возврата вызывающей стороны |
Потеряли coroutine_handle незавершённой корутины | Утечка памяти: frame в куче никто не освободит |
return вместо co_return в теле корутины | Ошибка компиляции — в корутине разрешён только co_return |
Бросили исключение из final_suspend() | UB — стандарт требует noexcept |
Прямой handle.resume() внутри await_suspend для цепочки корутин | Stack overflow на длинных цепочках; нужен symmetric transfer |
initial_suspend = suspend_never + ожидание получить результат | Тело уже выполнилось до возврата объекта — слишком поздно подписываться |
co_await объект, у которого нет всех трёх методов Awaitable | Ошибка компиляции при инстанцировании, не всегда понятная |
| Использование корутины как «асинхронной функции» без планировщика | Корутина приостановилась — и никто её не возобновит |
coroutine_handle::address() сохранили, frame разрушили | Висячий хендл — resume() это UB |
Захват *this в method-корутине, объект умер до resume | Frame ссылается на разрушенный объект |
Значение для собеседований
Корутины — относительно свежая тема (C++20), и в 2024–2025 они всё активнее появляются на собеседованиях в компаниях с развитой инфраструктурой C++ (game dev, HFT, сетевые сервисы). Большинство кандидатов знают синтаксис, но плохо понимают механизм.
Что обычно проверяют:
- Понимание coroutine frame — где живёт, кто управляет временем жизни, можно ли элиминировать аллокацию (HALO).
- Разницу между
co_await,co_yieldиco_return— не синтаксически, а семантически. promise_type— какие методы обязательны, что делаютinitial_suspendиfinal_suspend.- Awaitable-концепцию — три метода и три варианта возврата
await_suspend(void,bool,coroutine_handle<>). - Чем корутины отличаются от потоков и почему они не запускаются «автоматически» в фоне.
- Что такое symmetric transfer и от какой проблемы он спасает.
- Написать минимальный генератор на C++20 без
std::generator— классический live-coding вопрос.
Типичный неверный ответ: «Корутины — это асинхронные функции, как async/await в C#». Это пускает в нужное русло обсуждение, что корутина — это приостановленная функция, а асинхронность — лишь один из сценариев применения наряду с генераторами, state machine и кооперативной многозадачностью. Ключевое слово — suspension, а не async.