Комментарии 27
Функции couroutine_states описание стейт-машину согласно пользовательскому набору вызовов
Какой язык моя только что прочитать?
И где ссылка на оригинал? Почему перевод не оформлен как перевод? Ни за что не поверю, что это автор всё сам написал — на родном языке так не пишут.
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 уже написана.
Лучшая неофициальная обертка вот здесь — github.com/lewissbaker/cppcoro
Блин, я пользовался корутинами еще в году эдак 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% использовать (когда допишут) то, что предлагается библиотекой.
Псевдопараллельное выполнение программы - коротко и ясно.
Чтобы были. Когда они в языке есть — вы можете при желании и свой scheduler написать, и что угодно с ними сделать. Когда их в языке нет — то вы можете только язык сменить, если они вам внезапно понадобились.
Есть отличное выступление Ивана Пузыревского про асинхронность https://www.youtube.com/watch?v=g7dno0SupKY, в котором он очень развернуто отвечает на вопрос.
В отличии от cppcoro, которая реализует ленивые корутины, эта библиотека реализует неотложные корутины.
C++20. Coroutines