Как стать автором
Обновить

Комментарии 27

Функции couroutine_states описание стейт-машину согласно пользовательскому набору вызовов

Какой язык моя только что прочитать?


И где ссылка на оригинал? Почему перевод не оформлен как перевод? Ни за что не поверю, что это автор всё сам написал — на родном языке так не пишут.

Возможно, у автора английский давно уже на первом месте, если он сам это написал.

Спасибо за замечание, исправил.
Это не перевод. Ссылки на использованный материал (его большая часть) находятся в конце статьи.
И отдельная благодарность за найденные ошибки: oficsu, novoselov, HepoH, HungryD, TAPro, ximen, Mingun. Спасибо!

s/ни как/никак/
Пожалуйста!

Как же сложно написано. Автору платят за количество букв, или что?
А может это тема сама по себе сложная? А не, чтож это я…
Отличная статья, спасибо! Очень хорошо что теоретическая часть охватывает все возможные варианты сопрограмм. С практической надо экспериментировать в отладчике, так сходу непонятно.
А что в библиотеках boost — там сопрограммы какого типа? И в других языках? Было бы интересно еще вот такой обзор и сравнение реализаций.

В Go lang горутины это что-то больше похожее на GNU Pth с вытесняющим кооперативным планировщиком в user-space, но, как где-то прочитал: it looks and feels preemptive. На C++ для подобного можно попытаться использовать libgo, они там вытягивают кусок из Boost.Context для реализации функционала сохранения и переключения контекста. В обоих случаях нужно запускать планировщик в отдельном треде, что очень похоже на работу планировщиков всяких RTOS типа FreeRTOS, ThreadX. При этом в коде не нужно специально писать всяких co_yield, но нужно очень осторожно использовать всякие std::mutex. До какой-то версии в Go lang, горутина не прерывалась/вытеснялась до блокирующих операций, типа чтения из канала, сна, yield и т.п. Опять таки, это очень похоже на работу планировщиков RTOS, которые делают решедулинг на блокировках мутексов (если поток должен уйти в ожидане), эвент-группах и т.п.


В Lua сопрограммы реализуются стандартной библиотекой (ну… насколько она может быть оторванной от языка) coroutine. Для корутины создаётся "объект", над которым выполняются операции, управление чётко кооперативное, без вытеснения и планировщика, передача управления по coroutine.yield() и возврат по coroutine.resume(object). В общем, согласно документации раз и презентации два, это stackful асимметричные корутины и объекты первого класса. А по поведению они конкретно очень близки академическому определению сопрограммы: подпрограмма (функция) имеет одну точку входа и несколько точек выхода (return), то сопрограмма (корутина) имеет стартовую точку входа (аналогично подпрограмме) и несколько точек выхода (yield) с последующим за ним входом (resume).


В Boost обе реализации — stackful03. Boost.Coroutine2 — ассиметричные корутины, требует C++111, используют Boost.Context1 для переключения контекста. Boost.Coroutine не предъявляет требования к C++112 и предоставляет как симметричные, так и асимметричные корутины3, объявлен как deprecated2. Для передачи управления используются библиотечные средства: pull_type, как аналог resume, push_type, как аналог yield. В данном аспекте оно очень похоже на корутины в Lua. И так, как они movable, то их можно передавать как аргументы, это ещё и объекты первого класса.


Есть ещё Boost.Fiber, вот они реализуют что-то похожее на потоки (с соответствующим API, походим на текущие threading API) в пользовательском пространстве, но с кооперативным планировщиком (вызывается по this_fiber::yield(), на примитивах синхронизации и т.п.). Поэтому просто взять и сделать read(fd, ...) уже не получится (читаем, читаем, читаем).

Насколько легко будет отлаживать? Насколько легко будет понимать тексты ошибок и предупреждений компилятора?
Думаю, что в отладке они будут сравнимы с аналогичными решениями на коллбеках

А касательно текстов ошибок — нужно какое-то время, чтобы увидеть хороший результат, потому что в недавних релизах GCC можно было с лёгкостью увидеть и сообщение об ошибке в духе «internal compiler error: Segmentation fault»
Я вот его не понимаю, неужели предполагается, что мы должны писать этот бойлерплейт? Или это будет добавлено в мифическую рантайм-библиотеку корутин в будущем?

Какой именно бойлерплейт? Да, почти весь приведённый тут код должен быть в библиотеках.


И, к слову, не такие эти рантайм-библиотеки мифические, библиотека cppcoro уже написана.

Таски, промисы и прочее.
Это всё выглядит как мануал для написания куска стандартной библиотеки, а не что-то готовое к продакшну.

Это всё выглядит как детальный разбор происходящего под капотом.

Пока да, в С++23 стандартизуют хайлевельную обертку.

Лучшая неофициальная обертка вот здесь — github.com/lewissbaker/cppcoro
Это типа Таsk в C#? Незаменимая вещь надо сказать.

Блин, я пользовался корутинами еще в году эдак 87, на допотопных машинах с 32 КБ памяти. Пользовался легко и непринуждённо. Описание корутин в том языке (SPL — System Programming Language) занимало максимум полторы странички мануала и понималось даже кухарками. После этой же статьи ощущение такое: "корутины? — никогда!". Ну и русский язык, конечно, хромает на обе лапы.

Расскажите. Всегда интересно посмотреть на что-то с разных точек зрения.
Мне например интересно сформулировать максимально простую и понятную концепцию корутин, включая максимально простой и понятный синтаксис.
Да очень просто. Корутины — это когда две функции как бы вызывают друг друга (точнее, когда вызываемая функция продолжается с места возврата при следующем вызове). То есть, каждая продолжается с места вызова другой. Конкретный синтаксис не помню, но очевидно вызываемой корутины было два варианта оператора возврата: окончательный возврат, как у обычной фунции, и возврат с запоминанием текущего состояния.

Тут вопрос гибкости. Если взять что-то типа libgo или co и пилить код вида:


go [=]() {
  uint32_t message;
  read(fd, &message, sizeof(message));
};
go [=]() {
  uint32_t message = random();
  write(fd, &message, sizeof(message));
};

То всё намного проще выглядит. Но да, тут Stackfull, а это уже особенности и платформы и архитектуры, нужно как-то реализовывать сохранение контекста. С тем же успехом можно и пользоваться pth.


Здесь же Stackless корутины, они проще в реализации с точки зрения низкого уровня. И стандарт на уровне Coroutine TS вводит только самый низкий уровень поддержки. Что бы его полноценно использовать, нужна законченная поддержка со стороны библиотеки. Здесь очень много кода, который, по сути, должен быть в библиотечной части. Зато если всё понять и осознать, можно потом очень тонко рулить поведением, когда потребуется. А в 90% использовать (когда допишут) то, что предлагается библиотекой.

Псевдопараллельное выполнение программы - коротко и ясно.

Зачем вообще нужны корутины в C++? В идеале придётся писать scheduler, что-бы не заниматься ручным управлением корутин. Или именно ручное управление ими должно приносить какую-то пользу?

Чтобы были. Когда они в языке есть — вы можете при желании и свой scheduler написать, и что угодно с ними сделать. Когда их в языке нет — то вы можете только язык сменить, если они вам внезапно понадобились.

Есть отличное выступление Ивана Пузыревского про асинхронность https://www.youtube.com/watch?v=g7dno0SupKY, в котором он очень развернуто отвечает на вопрос.

у корутин есть киллер фича — быстро заснуть сохранив состояние и быстро проснуться его загрузив. Плюс некоторый синтаксический сахарок. При этом обвязку, а-ля библиотеку корутин — таски/шедулеры/екзекьюторы вы напишете один раз.
Хочу добавить ещё одну библиотеку: https://github.com/David-Haim/concurrencpp/
В отличии от cppcoro, которая реализует ленивые корутины, эта библиотека реализует неотложные корутины.
Зарегистрируйтесь на Хабре, чтобы оставить комментарий

Публикации

Истории