Корутины C++
Feb. 23rd, 2025 09:36 pm![[personal profile]](https://www.dreamwidth.org/img/silk/identity/user.png)
В С++20 появились корутины
Штука удобная, а стандарт написан так, что реализацию можно подставить какую хочешь. Это удобно. Но основная проблема - говорят, что человек может одновременно держать в голове 5..6 сущностей, программист - 7..8. Для реализации движка корутин нужно около 12.
Так с наскоку разобраться, как оно там под капотом работает, тяжело. Вроде все понятно, начинаешь делать - начинаются странности. При помощи ютуб - более-мене реально, но тоже сложно.
Я попытался сделать asyc/await движок с qt-шной очередью событий. (знаю про qcoro, но мне хочется понять, как оно сделано. А еще в qcoro нет await_all/await_first, и некоторые другие вещи не поддержаны). За 1 вечер нормально движок не сделать.
task<int> foo(){ ... }
int t = co_await foo();
int t = co_await foo();
Штука удобная, а стандарт написан так, что реализацию можно подставить какую хочешь. Это удобно. Но основная проблема - говорят, что человек может одновременно держать в голове 5..6 сущностей, программист - 7..8. Для реализации движка корутин нужно около 12.
Так с наскоку разобраться, как оно там под капотом работает, тяжело. Вроде все понятно, начинаешь делать - начинаются странности. При помощи ютуб - более-мене реально, но тоже сложно.
Я попытался сделать asyc/await движок с qt-шной очередью событий. (знаю про qcoro, но мне хочется понять, как оно сделано. А еще в qcoro нет await_all/await_first, и некоторые другие вещи не поддержаны). За 1 вечер нормально движок не сделать.
no subject
Date: 2025-02-23 08:04 pm (UTC)https://vak.dreamwidth.org/756289.html
https://vak.dreamwidth.org/756930.html
https://vak.dreamwidth.org/757005.html
https://vak.dreamwidth.org/757609.html
no subject
Date: 2025-02-23 09:39 pm (UTC)И ссылки, которые идут по ходу дела.
"очень много скрытой механники" - говорит лектор
no subject
Date: 2025-02-23 08:09 pm (UTC)no subject
Date: 2025-02-23 08:39 pm (UTC)В этом случае ты пишешь так, как будто это обычный "линейный" код. А под капотом оно не блокируется, не запускает миллион потоков.
Очень удобная штука.
no subject
Date: 2025-02-23 08:47 pm (UTC)no subject
Date: 2025-02-24 07:45 am (UTC)то что раньше называлось корутинами и сопрограммами, то и сейчас называется. Если вы не видите преимущества - тут 2 варианта-антогониста:
1. может просто ваш круг задач не предполагает надобности в этом инструменте. Если это так, то они действительно ненужны.
2. вы привыкли и просто не хотите разбираться с чем-то новым. Мол, задачи решаемы старыми методами - ну и славненько, а разбираться некогда или лень.
Скорей всего, у вас вариант 1. Потому что, когда много внешних событий, то без корутин их разруливать реально тяжело, получается "макаронный код", куча функций, как они связаны между собой сразу неясно, алгоритм не просматривается, все глючит. И поэтому обычно человек, ознакомившийся с корутинами скажет "оо! это то что надо для моих задач!".
no subject
Date: 2025-02-24 09:19 am (UTC)Так ещё два вопроса. Передаём ли мы callback (тогда всё в порядке), или та корутина знает мои точки входа (тогда бардак, чисто с топологических соображений). Вот этот второй вариант и привёл меня когда-то к выводу, что лучше не надо.
Погуглил на вики. Да, я сильно против. Топология получается хреновая. Если что сломалось, то дыру не найти, вообще говоря.
no subject
Date: 2025-02-24 09:50 am (UTC)возможно, вопрос сформирован как-то некорректно. Я не понимаю, что спрашивается. Я попытаюсь ответить, но возможно это будет ответ невпопад:
В с++20 корутинах ты сам делаешь библиотеку(или готовую находишь), которая как тебе надо диспетчеризует исполнение по потокам, и в нужные момент зовет нужные континьюэйшены из нужных тебе потоков.
> Так ещё два вопроса. Передаём ли мы callback (тогда всё в порядке), или та корутина знает мои точки входа (тогда бардак, чисто с топологических соображений). Вот этот второй вариант и привёл меня когда-то к выводу, что лучше не надо.
Передать каллбэк можно, и работать будет, но это не часть механизма корутин. Я бы назвал каллбеки до-корутинным способом написания асинхронного кода.
Недостаток каллбеков в том, что когда у тебя много вложеных событий, то получается много вложенных каллбэкеов. И получается то, что назвали callback hell, и собственно ради отказа от callback hell эти корутины и придумали. В гугле вводите callback hell и тыкаете картинки, там будут примеры кода.
А если каждой асинхронной операции, например, нужен свой таймаут, а иногда таймаут нужен один на несколько операций, то там еще и ветвление асинхронное получается. Делал такое, больше не хочу. Это вообще жуть.
> Если что сломалось, то дыру не найти, вообще говоря.
На счет сишных корутин я не стану вообще ничего утверждать, т.к. почти не имел с ними дел, а только предварительно ознакомился. В kotlin корутины сделаны удобно, и библиотека уже есть. Я вообще не представляю, как сложно бы было некоторые вещи без них делать, но сама концепция вроде похожа на сишные.
Как таковой с++, даже без корутин, отлаживать мне тяжелей чем java/kotlin, поэтому понятно, что и с корутинами тоже будет тяжелей. Но если где-то какой-то баг понадобится искать - ну мне кажется, оценочно, что это так же тяжело будет, как и вообще все на с/с++. Поэтому я больше джаву-котлин люблю.
no subject
Date: 2025-02-24 11:43 am (UTC)Получаются обычные spaghetti. Которые нельзя алфавитно-топологически отсортировать. В которых может произойти бага, которую и теоретически невозможно локализовать. Это будет бюрократия, где не найти концов. (У нас в конторе примерно такая хрень, но у нас у разных групп сервисов репутации очень разные, и всегда можно свалить на "тех идиотов".)
no subject
Date: 2025-02-24 01:39 pm (UTC)да вроде наоборот оно задумывалось для "расспагечивания".
Наверное, или вы недопоняли суть, или я недопонял масштаб проблемы :)
Ну вот например, есть кнопка, и надо скачать последовательно 5 картинок с таймаутом. И если на кнопку нажали, то надо отменить скачку текущей скачиваемой картинки.
даже без кнопки и без таймаутов псевдокод построенный на каллбеках будет выглядеть условно так
Это будет отправной точкой. Теперь, чтобы добавить таймауты, их придется каждый раз подписывать-отписывать, и то же самое с кнопокой. Плюс навешивать событие по таймауту/кнопке отписывающее текущую скачку и начинающее следующую. Я даже не возьмусь это описывать псевдокодом: тупо много писанины.
Корутины все "выравнивают" и ты пишешь как будто код последовательный:
А когда надо навесить таймауты и кнопки - то используешь комбинаторы:
При этом комбинатор "точтозавершится_первым", запускает все 3 процесса сразу, но при завершении кого-то одного, остальные два процесса отменяются.
Из кода пропадает вот эта вложенность, события, подписки-отписки: избавляемся от вот этих самых spaghetti, которые все так не любят. И сразу становится видно, что именно делаешь. Поэтому количество багов уменьшается.
При этом предполагается, что либу поддержки корутин ты 1 раз отладил и забыл.
no subject
Date: 2025-02-24 01:47 pm (UTC)no subject
Date: 2025-02-24 02:36 pm (UTC)предполагается, что "скачать" возвратит байтовый массив, или какой-то объект с картинкой. таймаут возвратит void, в простейшем случае. нажатие кнопки - в простейшем тоже возвратит void, ну или например возвратит, правой или левой кнопкой мышки нажато.
И вот оно общение: я прошу "дай картинку" - а мне через какое-то время ее возвращают, или говорят "сорри, таймаут/кнопка отмены". Но при этом код я пишу, как будто последовательный, что существенно его упрощает и делает наглядным.
Чуть сложней дело обстоит с генераторами. Их вроде доделали полностью в с++23. А может и в 20, точно не скажу.
Вы вроде скалу знаете? Помоему в скале генераторы есть - но я не уверен. Когда-то очень давно читал книжку по скале - но не более того. Генератор это почти то же самое, что поток данных, только каждая следующая порция данных приходит через неопределенное время. Условно псевдокод:
no subject
Date: 2025-02-24 02:53 pm (UTC)Still, it's all pretty innocent. Not the kind of coroutines I knew, where entities call each other with no control regarding who can call whom (and why).
no subject
Date: 2025-02-24 01:39 am (UTC)no subject
Date: 2025-02-24 08:10 am (UTC)все локальные переменные и служебные структуры выделяются на куче при первом вызове, и потом твой механизм в нужном порядке стартует, тормозит, продолжает, бросает/перехватывает исключения, диспетчерезирует по потокам, удаляет корутины, которые должны отмениться "в середине исполнения". Так же, твой механизм ответственен за запуск корутины и за проброс результата ее исполнения вызывающей стороне, причем как в случаях, когда ты ее вызываешь из не-корутинного кода - так и в случаях, когда одна корутина зовется из другой.
И так же, никто не сказал, что старт/ожидание/завершение корутин должно быть строго вложенным. Одна корутина может запустить другую корутину, и они могут обмениваться друг с другом данными и что-то с данными делать (генераторы/co_yield). Получается: чуток одна поработала, выдала результат - потом другая использует этот результат. Поработала-поработа - потом снова первая работает дальше, и так далее.
Это все надо как-то разруливать, и есть сколько-то точек, в которых ты должен все это поведение описать.
no subject
Date: 2025-02-24 09:12 am (UTC)no subject
Date: 2025-02-24 11:21 am (UTC)