Здравствуйте, Evgeny.Panasyuk, Вы писали:
EP>Сам язык C++ говорит что в случае "выстрела" происходит UB, что в том числе разрешает среде действовать как ей требуется оставаясь в рамках языка — например подставить бронированный щит от таких пуль.
а что надо будет написать, чтобы управиться с UB ?
Здравствуйте, neFormal, Вы писали:
EP>>Сам язык C++ говорит что в случае "выстрела" происходит UB, что в том числе разрешает среде действовать как ей требуется оставаясь в рамках языка — например подставить бронированный щит от таких пуль. F>а что надо будет написать, чтобы управиться с UB ?
Здравствуйте, meadow_meal, Вы писали:
_>>>(хотя бы потому, что более высокоуровневый и специфичный язык позволяет более кратко формулировать решения характерных для данного класса задач проблем). EP>>Что конкретно имеется в виду (оставив за скобками динамику)? Тут ведь речь про сам язык, а не платформу? _>Когда мы говорим об эрланге как dsl для написания конкуррентных и распределенных приложений, нет смысла разделять язык, библиотеку и платформу, так как эта троица определяет наш алфавит.
ОК, что мешает сделать такую же платформу для C++?
_>Я могу послать сообщение другому процессу не беспокоясь (до определенного предела) о том, находимся ли мы на одной ноде или разных.
Это про платформу. Выше же (в выделенном) ты говорил про сам язык. Я поэтому и спрашиваю что там такого в языке.
_>>>что не нужно на С++ повторять модель Эрланга EP>>А почему бы не повторить там где это уместно? Erlang же не идеален, даже в рамках задач которые хорошо решаются на нём. Например та же динамика — насколько она там нужна?
И всё же, какой ответ? Или ответ только в динамике?
_>Не то чтобы нужна, но сильно упрощает hot code swapping (а это огромное счастье, и не только в поддержке, но и в разработке).
Я выше задавал вопрос (про практический опыт) на эту тему, но безрезультатно. Переадресую его тебе:
EP>Всякие штуки типа hot code loading или какие-нибудь живые monkey patch'и — это одно из главных преимуществ динамических языков.
EP>Интересно послушать про твой опыт использования подобного в контексте реальных боевых программ на Erlang'е.
_>Ну и я не понимаю недоверия перед динамикой, ошибки типизации редки и большая часть их отлавливается диалайзером, ну а скорость — бывает достаточной.
Эту скорость можно получить практически не теряя в удобстве, а в некоторых аспектах даже приобретая.
Это вполне себе аргумент в пользу реализации модели Erlang'а в статически типизированном языке.
EP>>Например на stackless coroutine можно запустить хоть миллиард "процессов". _>Наверное можно, но это же не повторение Эрланга. У процессов Эрланга изолированная память (для начала).
Пусть эти процессы также не общаются и в C++.
Или речь о механическом/языковом запрете? Это может обеспечить среда, например с помощью верификатора кода запрещающим разделяемое изменяемое состояние и т.п.
Здравствуйте, Evgeny.Panasyuk, Вы писали:
_>>Я могу послать сообщение другому процессу не беспокоясь (до определенного предела) о том, находимся ли мы на одной ноде или разных.
EP>Это про платформу. Выше же (в выделенном) ты говорил про сам язык. Я поэтому и спрашиваю что там такого в языке.
Да я вроде уже объяснил. Говоря об эрланге как о dsl, какой смысл спорить о том, про платформу ли отправка сообщения или про язык (языковой оператор "!"). Мне важно, что мой DSL это может. Конечно, в первую очередь дело в платформе.
Что касается языка безотносительно платформы — из очевидных достоинств — паттерн матчинг, иммутабельность, функциональные списки, bit syntax (очень удобная вещь), list/map/binary comprehension, метапрограммирование через parse transform, безлимитные целые. Ну и простота и читабельность конечно (а не набор смайликов как C++).
EP>И всё же, какой ответ? Или ответ только в динамике?
Так как мои ответы вам не подходят, имеет смысл уточнить вопрос.
_>>Не то чтобы нужна, но сильно упрощает hot code swapping (а это огромное счастье, и не только в поддержке, но и в разработке).
EP>Я выше задавал вопрос (про практический опыт) на эту тему, но безрезультатно. Переадресую его тебе: EP>
EP>>Всякие штуки типа hot code loading или какие-нибудь живые monkey patch'и — это одно из главных преимуществ динамических языков.
EP>>Интересно послушать про твой опыт использования подобного в контексте реальных боевых программ на Erlang'е.
В разработке очень удобно, например, у нас ребята часто работают парами, серверный программист и клиентский (например, Unity3d). Когда клиентский в игровой сессии тестирует новую функциональность или ловит баг, серверный может поправить код и нажать Ctrl+S, и это сразу отразится на том, что видит клиентский, ему не нужно даже игровую сессию перезапускать, перелогиниваться. С С++ можно пойти чаю попить, пока перекомпилируется все, пока заново в игру зайдешь, пока проблему воссоздашь. Конечно, с С++ и воркфлоу другой будет. Но поверьте, привыкнув к моментальному применению всех изменений кода, очень сложно от этого отказаться.
В продакшне — мы используем релизы, но не используем application upgrade. Monkey patch'и очень широко используем на dev и qa серверах, и в критических случаях на живых. Если есть возможность лишний раз не огорчать игроков дисконнектом, что ж ей не пользоваться.
Ну и интроспекция конечно — remote shell и богатые возможности исследовать возникающие проблемы в рантайме — дорогого стоит при трудноуловимых (и не только) багах.
_>>Наверное можно, но это же не повторение Эрланга. У процессов Эрланга изолированная память (для начала).
EP>Пусть эти процессы также не общаются и в C++. EP>Или речь о механическом/языковом запрете? Это может обеспечить среда, например с помощью верификатора кода запрещающим разделяемое изменяемое состояние и т.п.
Речь идет о том, что порча памяти одного процесса (по любым причинам) не повлияет на другие. Не вполне понимаю, как это возможно обеспечить для корутин в C++, но допустим возможно. Дальше вам придется реализовать идентификаторы процессов (корутин), отправку сообщений, мониторы/линки (без них не получится "супервизить", а без супервизоров о какой fault tolerance речь?). Рано или поздно дойдете и до Армстронговской "глючной и неполной реализации Эрланга".
Вот вам уже понадобился верификатор кода. Все можно, но как-то сложно.
Чтобы предотвратить лишний спор — я не пытаюсь сказать что чего-то нельзя, только что от корутин до эрланга огромный путь.
суть не изменилась. никто не понял, но все спорят
S>Так что не искажайте чужие слова и не приписывайте результат своим собеседникам, а то вся суть дискуссии сведется именно к этому.
Здравствуйте, Ikemefula, Вы писали:
V>>А почему не Эрланг, хотя бы? I>Технически, в вебе эрланг может заменить нод примерно в 100% случаев
Именно что. Зато наоборот — дудки.
Поэтому, вопрос — нафига ты привел Node.js как пример? ))
Чтобы над тобой заведомо улыбались, а ты в ответ хамил?
V>>Показал многократно. Найдешь то обсуждение, найдешь весь необходимый код. I>Это мягко говоря, враньё.
Ты меня утомил еще тогда. Тебе объяснили и логику и код показали, но ты продолжал залетать вновь и вновь, утомив в итоге всех участников. Т.к. прошло уже достаточно времени и ты должен был подрасти, рекомендую пройтись по той ветке с новым пониманием. На тот момент понимания было ровно ноль.
V>>Конечно, дотнет это не детсад, но именно библиотека Task безбожно тормозит в сравнении с идентичными по архитектуре библиотеками TBB/PPL. I>Это слабая отмазка. Ты рассказывал про нод, а проблемы с асинхронщиной оказались в дотнете.
Хы ))
Это как раз ты рассказывал про нод, про который у человека с хоть каким-нибудь кругозором может быть один вердикт — детсад для детсада. В крайнем случае этот тул подойдет для прототипирования и для каких-нить внутренних утилит сомнительного кач-ва. На этом тему нода предлагаю закрыть, во избежание эскалации насмешек в эту сторону.
V>>Ну как это БЕЗ жестких данных, если я рядом же в обсуждении дал затраты порядка 2-3 uSec задержек с картеек onload в юзер-мод драйверах? I>Смешно. Асинхронщина в дотнете гораздо тормознее.
Угу, аккурат на порядок.
V>>Взял бы да замерил сам как-нить задержки дотнетного асинхронного IO. I>Меня забавляет, что дотнет у тебя выступил аргументом против нода.
Если бы этот пост писал кто-нить другой, то я бы предположил, что его акк вскрыли ))
Ты, не обладая инфой, проехался по С++, приведя как аргменты некие другие технологии: http://rsdn.ru/forum/flame.comp/6069181.1
Здравствуйте, Ikemefula, Вы писали:
I>Нод испльзует libio унутре. Один поток даст слишком большие задержки при большом количестве параллельных запросов. I>Дело в том, что IO не вызывает JS сразу, как только получит его. Запрос проходит довольно большой препроцессинг прежде чем попадёт в JS.
У вас устаревшая информация — libeio был полностью заменен на libuv где-то пару лет назад точно, в node.js версии 0.8. Емнип, разработчики node.js заколебались бороться с libeio и с 2011 года стали делать свой вариант асинхронного ввода/вывода.
Внутри libuv банальный пул потоков (по умолчанию вроде бы 4) в котором выполняется ввод/вывод на файлах и сокетах.
Здравствуйте, Evgeny.Panasyuk, Вы писали:
EP>А зачем копировать внешний мир?
Ну потому что корутина нужна не для галочки, а для обслуживания сигналов из внешнего мира.
V>>Т.е. какова семантика происходящего при этом EP>Копируются/перемещаются все созданные поля плюс индекс текущего состояния этого конечного автомата.
Т.е. хендл сокета тоже копируется? А дальше как?
Вот мы начали асинхронную операцию, после этого вошли в "ожидание", то бишь, сделали этой корутине yield, потом сделали копию корутины. Теперь обе корутины "ждут" один и тот же сокет, но на самом деле, ждет только одна — экземпляр которой был зарегистрирован как обработчик события. Вторая корутина представляет из себя бессмысленный набор байт.
V>>и для чего это всё? EP>Например для того чтобы использовать обычные правила автоматических/stack объектов C++, а не аллоцировать кадр под корутину.
О каком "стеке" идет речь? После создания корутины надо выйти из текущего уровня стека, т.е. вернуть управление вызвавшему коду через future. А корутина путь живет себе и реагирует на события, продвигая как автомат своё тело. Это про stackless. Фишка в том, что stackless корутина шагает по своим состояниям в общем случае в РАЗНЫХ потоках, из которых события приходят по push. Т.е., вот есть две асинхронные операции внутри такой корутины, оповещение по каждой из них продвигает корутину-автомат, но это оповещение приходит для каждой операции из другого потока. Поэтому, я не очень понимаю, что значит "правила автоматических объектов" для этого сценария.
EP>Для того чтобы можно было легко передавать её из одного места в другое.
Как раз по указателю она передаётся легко.
То, что ты говоришь — оно интересно только для генераторов/трансформаторов и только для модели poll, типа как IEnumerable<>/yield в дотнете.
Поверь, даже в дотнете это наглухо совсем другое, чем async/await, хотя происходящее при этом как бы близкое. ))
EP>Для того чтобы генераторы (т.е. yield value) — были ForwardIterator а не single pass InputIterator.
Это решается оберткой-итератором один раз и для всех таких корутин. Спасибо за уточнение, я хоть понял, куда ты клонил.
Я думаю, что тут лучше не рассуждать о корутинах вообще, а применить этот подход прямо по назначению — развить концепцию итератора.
Потому что никакой универсальности, а отличие от resumable-ф-ий. Потому что только poll, и потому что только обязательный внешний шедуллинг, управляемый дынными, который уместен как раз в концепции итератора (оно же генератора/трансформатора) и нигде более.
EP>Для того чтобы можно было такую корутину сериализовать по сети.
Ну это сфероконские мечты. Как ты будешь сериализовать корутину, которая обрабатывает сокет?
Я же говорю, упомянутое тобой — совсем отдельный класс задач.
EP>>>future тут вообще не причём, stackless coroutine как фича языка должна брать код/функцию обычного вида и трансформировать её в класс-автомат, где поля это переменные из тела функции, а состояния — точки останова yield. В C# yield именно таким образом и реализован. V>>Если брать C#, то там для аналогичного служит async/await, где async является аналогом resumable из proposal для C++17. В обоих случаях сигнатуры async/resumable возвращают идентичный по назначению объект — Task/future. EP>Сигнатура тут тоже не причём.
Увы, это принципиально. Без future нет await, без await нет возврата управления вызывающему коду (в дотнетном Task и плюсовых TBB/PPL — это потоковая процедура рабочего потока из пула потоков, где тело этой потоковой процедуры выполняет work stealing алгоритм, т.е. по await происходит возврат управления шедуллеру).
Понимаешь, в resumable-методах не мы делаем этому методу resume (как в случае с итератором/генератором/трансформатором), а именно что future в момент изменения своего состояния.
Я же говорю, это модель push vs poll.
EP>>>future и подобное появляются только в частных случаях корутин применения типа await. V>>Дык, именно для await это всё, для этой самой точки автоматической передачи управления следующей задаче из очереди задач текущего потока. EP>Для этого в том числе. НО, это также достигается другими, лучшими методами.
Пока никто не показал.
Не приводи как аргумент stackless корутины из буста, плиз )
Потому что это фигня в сравнении с await, если честно.
Потому что никакого контроля со стороны компилятора. Потому что одним неверным чихом можно всё поломать.
Я реально игрался с корутинами на дотнете еще до async/await, делал их на IEnumebrable/yield. Очень быстро уперся в то, что адекватно эта корутина работает только на одном уровне. Но я ж, типа, не сдался (10 лет назад было), начал городить кучу хелперов для автоматического "ныряния" итератора внутрь за логикой yield во вложенные ф-ии (тоже итераторы, ес-но) и быстро обнаружил, что ВСЕ вызовы, ВСЕ анонимные методы (лямбды) надо покрывать подобными хелперами, изобретая в итоге новый язык программирования с конским синтаксисом. ))
И да. Одним неверным чихом можно всё поломать и доооолго будешь искать — где.
На future/await поломать что-либо намного сложнее... И все-равно, по опыту дотнетного Task/await — даже в таких "тепличных" условиях народ с БОЛЬШИМ трудом рожает асинхронные алгоритмы. И практически никогда и ни у кого это с первого раза не работает. Се ля ви. ))
EP>Вот как раз в stackless корутинах из Boost.Asio и показано как это может быть реализовано по другому — там не нужно вызывать аллокатор для корутины, это просто класс, объект которого можно создать любым из возможных способов, его можно скопировать, перемещать, сериализовать и т.п.
Одна тонкость — это всё не нужно, в тех сценариях, для которых, собсно, корутины разрабатываются.
EP>Мне тут скинули ссылки, оказывается автор Boost.Asio сделал другой proposal N4244 — он как раз основан на дизайне stackless coroutine из Boost.Asio.
Это стоит назвать чем-то вроде итератора, дабы не вводить людей в заблуждение.
EP>Я надеюсь что proposal от Microsoft не пройдёт, а пройдёт от автора Boost.Asio.
Чур тебя))
Это ортогональные техники для непересекающихся сценариев.
Кстате, прошелся я по твоей ссылке на обсуждение, и первый же пост перед глазами:
You are right. Passing generator by reference to a function that you cannot see into the body, should inhibit this optimization.
Since lifetime can be stolen via move.
>> Unless generator<T> has unspecified and surprising semantics[1], no, it is not the same, In the examples I'm swapping the generator of 'baz' with another generator with a different lifetime.
What I meant is that applying heap elision optimization is unsafe and thus should not happen in this example.
Sure, generator is movable. Coroutine state is not. Therefore, if move happens or you cannot prove that it cannot happen as in you baz() example. Coroutine Frame Allocation Elision Optimization (CFAEO should be inhibited.
Я называл проталкиваемую тобой технику итератором-генератором-трансформатором, тут просто называют генератором. ))
Последняя выделенаня фраза — это то, что я писал тебе в предыдущем сообщении.
Здравствуйте, Evgeny.Panasyuk, Вы писали:
EP>Я надеюсь что proposal от Microsoft не пройдёт, а пройдёт от автора Boost.Asio (хотя там тоже есть некоторые моменты, и над ними тоже нужно работать, но они менее значительные).
Читаю и поражаюсь наивности автора:
4 Writing simple generators
Consider a simple generator that counts down from a specified starting value:
auto g = [n = int(10)]() resumable
{
std::cout << "Counting down from " << n << "\n";
while (n > 0)
{
yield n;
n -= 1;
}
};
и только у меня созрел полный возмущения вопрос: а как он думает оформлять останов???
как читаю далее:
Instead of returning a single value as in a normal lambda, we generate a sequence of values
using the yield statement. To obtain these values we call the generator as a normal function
object:
Улыбнуло.
Вернее, валяюсь от смеха. ))
Не выйдет. ))
Во-первых, семантика resumable означает однократный вызов ф-ии, но многократное её асинхронное продолжение.
В приведенном же сниппете сама ф-ия вызывается многократно, синхронно, что всё вместе автоматом создаёт проблему останова. Предложенное в бусте решение — выкидывать исключение — это жесть, на это никто не пойдет.
Так шта, быть твоим генераторам в виде бустовской либы ))
Здравствуйте, Ikemefula, Вы писали:
I>node, кроме того, влегкую позволяет юзать на клиенте и сервере ровно один и тот же язык, код и даже фремворки. Более того, JS становится вообще единтсвенным ЯП на проекте.
Здравствуйте, vdimas, Вы писали:
EP>>А зачем копировать внешний мир? V>Ну потому что корутина нужна не для галочки, а для обслуживания сигналов из внешнего мира.
Что ты понимаешь под копированием внешнего мира?
V>>>Т.е. какова семантика происходящего при этом EP>>Копируются/перемещаются все созданные поля плюс индекс текущего состояния этого конечного автомата. V>Т.е. хендл сокета тоже копируется? А дальше как?
Здесь хендл сокета это просто поле объекта. Если сокет не копируемый тип (как обычно и бывает), а только перемещаемый — то и корутина будет только перемещаемой — обычные правила C++.
V>Вот мы начали асинхронную операцию, после этого вошли в "ожидание", то бишь, сделали этой корутине yield, потом сделали копию корутины.
Если сокет не копируемый, то и корутина не скопируется.
V>>>и для чего это всё? EP>>Например для того чтобы использовать обычные правила автоматических/stack объектов C++, а не аллоцировать кадр под корутину. V>О каком "стеке" идет речь? После создания корутины надо выйти из текущего уровня стека, т.е. вернуть управление вызвавшему коду через future. А корутина путь живет себе и реагирует на события, продвигая как автомат своё тело.
Где она будет жить?
Например если это генератор вида (псевдокод):
generator<int> natural()
{
int i = 0;
while(true)
yield i++;
}
for(auto x : natural())
cout << x << endl;
То всё состояние генератора спокойно может жить как автоматический/временный объект. Если я захочу создать его в куче — то сделаю new natural{}, и т.п. — это обычный тип.
Proposal же от microsoft подразумевает что будет некоторая явная аллокация, которую компилятор может оптимизировать, а может и нет.
V>Это про stackless. Фишка в том, что stackless корутина шагает по своим состояниям в общем случае в РАЗНЫХ потоках, из которых события приходят по push.
Даже stackful корутина из Boost.Coroutine может шагать по своим состояниям в разных потоках, я даже делал пример на эту тему
.
V>Т.е., вот есть две асинхронные операции внутри такой корутины, оповещение по каждой из них продвигает корутину-автомат, но это оповещение приходит для каждой операции из другого потока. Поэтому, я не очень понимаю, что значит "правила автоматических объектов" для этого сценария.
Я про то что generator из примера выше это просто тип, объект которого можно создать разными способами:
generator a;
auto *b = new generator{}
new(buf) generator{};
auto c = make_unique<generator>();
auto d = make_shared<generator>();
EP>>Для того чтобы можно было легко передавать её из одного места в другое. V>Как раз по указателю она передаётся легко.
Покажи как ты будешь легко передавать generator из примера выше по указателю, а конкретнее где под него будет выделятся память.
В моём варианте это просто небольшой объект размером в пару слов, который можно легко копировать.
V>То, что ты говоришь — оно интересно только для генераторов/трансформаторов и только для модели poll, типа как IEnumerable<>/yield в дотнете. V>Поверь, даже в дотнете это наглухо совсем другое, чем async/await, хотя происходящее при этом как бы близкое. ))
Я не вижу смысла выбирать тяжёлую реализацию, которую трудно оптимизировать, с которой трудно работать, и которая предоставляет меньше возможностей.
EP>>Для того чтобы генераторы (т.е. yield value) — были ForwardIterator а не single pass InputIterator. V>Это решается оберткой-итератором один раз и для всех таких корутин.
И как это решается? Сохранением всех пройденных значений? Вместо того чтобы иметь два объекта по два слова?
V>Спасибо за уточнение, я хоть понял, куда ты клонил. V>Я думаю, что тут лучше не рассуждать о корутинах вообще, а применить этот подход прямо по назначению — развить концепцию итератора. V>Потому что никакой универсальности, а отличие от resumable-ф-ий. Потому что только poll, и потому что только обязательный внешний шедуллинг, управляемый дынными, который уместен как раз в концепции итератора (оно же генератора/трансформатора) и нигде более.
На тех stackless корутинах о которых я говорю — реализуется и await и генераторы, и т.п. Они мощнее по возможностям.
EP>>Для того чтобы можно было такую корутину сериализовать по сети. V>Ну это сфероконские мечты. Как ты будешь сериализовать корутину, которая обрабатывает сокет?
Ты говоришь про один из конкретных случаев. Я не говорю сериализация нужна во всех случаях, так же как и копирование.
Мьютекс нельзя сериализовать по сети — так что теперь откажемся от сериализации вообще?
V>Я же говорю, упомянутое тобой — совсем отдельный класс задач.
Который решается в рамках stackless корутин.
EP>>>>future и подобное появляются только в частных случаях корутин применения типа await. V>>>Дык, именно для await это всё, для этой самой точки автоматической передачи управления следующей задаче из очереди задач текущего потока. EP>>Для этого в том числе. НО, это также достигается другими, лучшими методами. V>Пока никто не показал. V>Не приводи как аргумент stackless корутины из буста, плиз )
Почему? Я же не говорю что их нужно повторять в точности до синтаксиса и всех неудобств.
V>Потому что это фигня в сравнении с await, если честно.
На них реализуется и await, и yield.
Да, есть синтаксические ограничения, неудобства и неоптимальности — но как раз для этого и нужно изменение языка.
V>Потому что никакого контроля со стороны компилятора. Потому что одним неверным чихом можно всё поломать.
Именно поэтому и нужна стандартная фича.
EP>>Вот как раз в stackless корутинах из Boost.Asio и показано как это может быть реализовано по другому — там не нужно вызывать аллокатор для корутины, это просто класс, объект которого можно создать любым из возможных способов, его можно скопировать, перемещать, сериализовать и т.п. V>Одна тонкость — это всё не нужно, в тех сценариях, для которых, собсно, корутины разрабатываются.
Это нужно как минимум для генераторов, и мигрирующий лёгких процессов.
EP>>Мне тут скинули ссылки, оказывается автор Boost.Asio сделал другой proposal N4244 — он как раз основан на дизайне stackless coroutine из Boost.Asio. V>Это стоит назвать чем-то вроде итератора, дабы не вводить людей в заблуждение.
Это именно stackless coroutine.
EP>>Я надеюсь что proposal от Microsoft не пройдёт, а пройдёт от автора Boost.Asio. V>Чур тебя)) V>Это ортогональные техники для непересекающихся сценариев.
Да ладно? И там и там реализуется await/async, и там и там реализуется yield — и в обоих случаях это является мотивирующими примерами
Здравствуйте, vdimas, Вы писали:
V>Читаю и поражаюсь наивности автора: V>
V>4 Writing simple generators
V>Consider a simple generator that counts down from a specified starting value:
V>
V>auto g = [n = int(10)]() resumable
V>{
V> std::cout << "Counting down from " << n << "\n";
V> while (n > 0)
V> {
V> yield n;
V> n -= 1;
V> }
V>};
V>
V>и только у меня созрел полный возмущения вопрос: а как он думает оформлять останов??? V>как читаю далее: V>
V>Instead of returning a single value as in a normal lambda, we generate a sequence of values
V>using the yield statement. To obtain these values we call the generator as a normal function
V>object:
V>
Здравствуйте, vdimas, Вы писали:
V>Кстате, прошелся я по твоей ссылке на обсуждение, и первый же пост перед глазами: V>
V>You are right. Passing generator by reference to a function that you cannot see into the body, should inhibit this optimization.
V>Since lifetime can be stolen via move.
>>> Unless generator<T> has unspecified and surprising semantics[1], no, it is not the same, In the examples I'm swapping the generator of 'baz' with another generator with a different lifetime.
V>What I meant is that applying heap elision optimization is unsafe and thus should not happen in this example.
V>Sure, generator is movable. Coroutine state is not. Therefore, if move happens or you cannot prove that it cannot happen as in you baz() example. Coroutine Frame Allocation Elision Optimization (CFAEO should be inhibited.
V>Я называл проталкиваемую тобой технику итератором-генератором-трансформатором, тут просто называют генератором. ))
Генератор это use-case который поддерживают оба варианта. Конкретно здесь говорится про генератор на базе корутин Microsoft, а не то что ты подумал.
V>Последняя выделенаня фраза — это то, что я писал тебе в предыдущем сообщении.
Так это в рамках proposal'а Microsoft state корутины не перемещаемы, да ещё и плохо оптимизируемы — о чём я и сказал в первом сообщении на тему.
Это сообщение одного из авторов этого proposal'а, в который он говорит что да — в этом случае оптимизация аллокаций его корутин невозможна То есть помимо меньших возможностей здесь ещё и меньшая скорость
У концепции "продолжения" (continuation), под реализацию которой затачивают корутины в С++17, не может быть никаких многократных заходов в одну и ту же точку входа, при том, что это выглядит как многократный вызов одной и той же ф-ии, которая, к тому же, однажды выплюнет исключение, Ы-Ы-Ы.
Генератор должен вернуть итератор. Далее этот итератор обслуживается так же как все остальные итераторы, через for(var a : iterator), через алгоритмы STL и через аргументы стандартных контейнеров.
Согласись, что интерфейс итератора должен заметно отличаться от простого вызова ф-ии, возвращающей следующее значение.
EP>То всё состояние генератора спокойно может жить как автоматический/временный объект.
Да ради бога. Только генератор/трансформатор — это лишь очень частный случай для техники корутин, который, к тому же, никогда не будет принят в том виде, в котором его предлагает один из авторов буста. ))
Да, твои генераторы и полноценные продолжения могут порождаться с помощью схожей механики (автомат представлен в виде линейного кода), наверно это вводит в заблуждение? ))
EP>Если я захочу создать его в куче — то сделаю new natural{}, и т.п. — это обычный тип.
Возможно, так и будет. Пройдись, плиз, по ссылке.
EP>Proposal же от microsoft подразумевает что будет некоторая явная аллокация, которую компилятор может оптимизировать, а может и нет.
Ну натурально, если непонятно, при чем тут связка await c std::future, то в следующую итерацию мы остановимся на этом моменте и не пойдём дальше, пока не преодолеем его. ))
В общем, я даю тебе установку (как Кашпировский) — основной сценарий вертится вокруг будущих расширений std::future (метод then). Твоя задача, даже если не согласиться с этим, но хотя бы понять, откуда ноги растут именно у этого направления.
И еще перестать волноваться, бо это "направление" — тоже лишь одно из направлений, правда, самое важное на сегодня. Думаю, "легковесный" сценарий тоже в итоге прикрутят.
EP>Даже stackful корутина из Boost.Coroutine может шагать по своим состояниям в разных потоках, я даже делал пример на эту тему
Более того, она должна вообще жить в условиях постоянных гонок.
Вот тебе пример:
future<int> foo(int x) resumable
{
auto y = await getY(x); // (1)return await getZ(y); // (2)
};
Обрати внимание на сигнатуру, это принципиально.
Итак, что здесь происходит в случае stackless-корутины:
— Мы вызываем foo(42) лишь однажды (сравнить с многократным явным заходом в твой генератор).
— В первый раз тело foo гарантированно выполняется до точки (1).
— getY возвращает нам future, у этого future мы вызываем метод then, который цепляет остаток тела foo как продолжение (в дотнете в этом месте вызывается Task.ContinueWith(restOfMethodBody)).
— возвращаем вызывающему коду некое зарезервированное под результат ф-ии еще одно future.
— если в момент вызова метода then у future в точке (1) getY уже получил результат, то продолжение метода вызывается мгновенно из текущего потока, т.е. мы сразу переходим к (2) и далее аналогично как с (1).
— если в момент вызова then в точке (1) данные от getY еще не готовы, то продолжение тела foo будет вызывано из того потока, в котором самое первое future перейдет в состояние resolved.
Если же речь о выполнении аналогичного кода в statefull-корутине, то в точке (1) мы должны будем запросить создать некий примитив синхронизации прежде чем вернем управление, а затем ожидать этот примитив синхронизации на каком-нить пуле потоков+IOCP для обработки в момент готовности примитива синхронизации. Что тут хреново — это переключение контекстов, т.е. примитив синхронизации будет установлен в сигнальное значение в одном потоке, а продолжение будет вызвано в общем случае в другом потоке и м/у этими двумя событиями быдет относительно тяжеловесное срабатывание шедуллера, либо ядерного (для случая IOCP), либо пользовательского для какой-нить аналогичной технологии. Всё это — затраты и еще раз затраты, в то время как продолжение, прилепленное по then вызывается сразу же по переводу future в состояние ready.
V>>То, что ты говоришь — оно интересно только для генераторов/трансформаторов и только для модели poll, типа как IEnumerable<>/yield в дотнете. V>>Поверь, даже в дотнете это наглухо совсем другое, чем async/await, хотя происходящее при этом как бы близкое. )) EP>Я не вижу смысла выбирать тяжёлую реализацию, которую трудно оптимизировать, с которой трудно работать, и которая предоставляет меньше возможностей.
А почему ты решил, что там будет идентичная реализация для разных сценариев?
Твой сценарий — это, считай, уровень 0 всей этой кухни. ))
Возможно, он так и будет доступен в легковесном варианте.
EP>На тех stackless корутинах о которых я говорю — реализуется и await и генераторы, и т.п. Они мощнее по возможностям.
Нет, не мощнее. Это просто базовый механизм для обсуждаемого. Я уже понял, что ты хочешь этот базовый механизм в явном виде. Но этого, ес-но НЕ будет никогда)) Потому что стандарт С++ для того и нужен, чтобы скрывать особенности платформ от целевого прикладного кода. ))
Поэтому, если даже твой сценарий и реализуют, то это будет чистенько, типобезопасненько и полностью совместимо с интерфейсом уже имеющихся итераторов, т.е. алгоритмов STL, контейнеров и всяких новых синтаксисов for(auto a : generate()) { ... }.
EP>Мьютекс нельзя сериализовать по сети — так что теперь откажемся от сериализации вообще?
Сериализовать надо состояние, которое подходит под определение "персистентное". Это всегда задача отображения данных на объекты и наоборот.
V>>Я же говорю, упомянутое тобой — совсем отдельный класс задач. EP>Который решается в рамках stackless корутин.
Не в рамках, а через механизм. Но этот механизм будет от тебя спрятан компилятором, ес-но.
EP>Почему? Я же не говорю что их нужно повторять в точности до синтаксиса и всех неудобств.
Ну потому что многократный явный вызов ЕДИНСТВЕННОЙ "точки входа" — это жесть, вообще-то. Это банально неумно. У интерфейса/контракта итератора должно быть больше одного метода, чтобы он был действительно полезной парадигмой.
V>>Потому что это фигня в сравнении с await, если честно. EP>На них реализуется и await, и yield.
И обратное тоже верно. Все эти техники можно будет выразить один через другого, и что с того? ))
Но не для того стандарт разрабатывают, чтобы прикладной код занимался подобными извращениями. Извращаться-то можно уже прямо сейчас.
V>>Потому что никакого контроля со стороны компилятора. Потому что одним неверным чихом можно всё поломать. EP>Именно поэтому и нужна стандартная фича.
Э, нет. Я говорю про асинхронные корутины, которые ты собрался делать на "обычных yield-генераторах". Там, действительно, легко ошибиться. Рано или поздно ты будешь пытаться вручную обыгрывать вложенность алгоритмов, а оно выливается во вложенность генераторов. Именно в этом месте, в задаче "правильного обхода" вложенных итераторов/генераторов и сидит огрооомная такая черная дыра — бесконечный источник досадных ошибок на ровном месте. Поэтому, корректным обходом вложенностей в случае stackless-корутин пусть занимается компилятор.
Это я делюсь реальным опытом с аналогичной технологией IEnumerable/yield из дотнета, когда первым делом захотелось организовать на этой технике пресловутые продолжения. Кароч, не взлетело. Т.е., взлетело, конечно (при достаточном запасе дури можно же что угодно заставить работать). Но оно не "взлетело" в кач-ве некоего "промышленно-рекомендуемого способа порождения корутин". Зато Task/async/await в дотнете произвели натуральную революцию именно в этой области, бо из области потенциальных ошибок исчезла область ручного обыгрывания механики происходящего и осталась только область ошибок прикладной логики, как и должно быть, собсно.
EP>Это нужно как минимум для генераторов, и мигрирующий лёгких процессов.
Не "как минимум", а только для них, родимых ))
Генераторы/итераторы/трансформаторы последовательностей — вот этот круг задач.
Это по опыту того же дотнета вот уже 10 лет как (со времени доступности первого yield).
Всё это работает через интерфейс итераторов, т.е. совместимо с "традиционной" окружающей средой.
Думаю, в С++ будет аналогично, бо напрашивается само.
EP>>>Мне тут скинули ссылки, оказывается автор Boost.Asio сделал другой proposal N4244 — он как раз основан на дизайне stackless coroutine из Boost.Asio. V>>Это стоит назвать чем-то вроде итератора, дабы не вводить людей в заблуждение. EP>Это именно stackless coroutine.
Ты используешь слишком широкое понятие, реально. ))
Да, в базе там stackless coroutine.
EP>>>Я надеюсь что proposal от Microsoft не пройдёт, а пройдёт от автора Boost.Asio. V>>Чур тебя)) V>>Это ортогональные техники для непересекающихся сценариев.
EP>Да ладно? И там и там реализуется await/async, и там и там реализуется yield — и в обоих случаях это является мотивирующими примерами
Но они НЕ взаимозаменяемы. У тебя НЕ будет способа выразить await через yield в простейшем же сценарии:
Тут две точки входа/выхода в продолжение — на await и на yield (даже если оптимизирующий компилятор сделает из них одну, логически их тут всё-равно две). А цимус в том, что yield должен быть "типизированным", т.е. его аргумент должен быть приводим к int. Т.е. ты НЕ сможешь использовать "типизированный" yield для "нетипизированной" точки входа/выхода await, то бишь вырразить await через yield в этом сценарии.
Получается, что взаимозаменяемость там дохлая, только для случая generator<void>.
Более того, при "ручном" yield в указанном сниппете ты должен будешь озаботиться шедуллингом, в то время как в предлагаемой схеме автоматом используется шедуллер, управляющий операцией s->readByteAsync(). А в предыдущем сниппете, в точках (1) и (2) в общем случае автоматом будут использованы РАЗНЫЕ шедуллеры, управляющие соответственно операциями getY и getZ.
Т.е. не всё так просто в случае "ручных" асинхронных корутин, не? ))
Здравствуйте, BulatZiganshin, Вы писали:
BZ>среди штук 5 акторных бибилотек. при этом ты ни словом не упомянул наиболее известные tbb/ppl/boost.coroutine и сказал что подход с потоками принципиально неприменим в C++. на мой же взгляд, акторы — это малопопулярная парадигма, а основная как раз сейчас — task paralleleism к постепенным переходом к асинку (stackful/stackless coroutines)
Ну так акторы хорошо живут в виде корутин.
Прикол еще в том, что в MS ConcRT есть класс agent.
Он наипростейший, в отличие от агентов SObjectizer-а, но, тем не менее, присутствует. ))
BZ>один вариант — это дерево задач, порождающих задачи и ждущих результата их исполнения. c++11 futures с доп. операциями типа waitAll и then.
Это ничем не отличается от порождения агентами новых агентов.
Future/await — это механика уровня 0, т.е. просто дисптчинг вычислительных ресурсов. В то время как всю механику обмена сообщениями м/у агентами в каждом прикладном случае надо будет делать ручками и только ручками. SObjectizer в этом смысле предоставляет относительно полноценный фреймворк для обмена сообщениями.
Более того, насколько я понял его работу, необязательно использовать встроенные в SObjectizer ср-ва для генерирования состояний автоматов и логики хождения м/у ними, можно же не использовать эту часть фреймворка, т.е. с т.з. вреймворка у агента может быть только одно состояние, а всё остальное (реакция на входящие сообщения) можно реализовать ручками, не затрагивая инфраструктуру.
BZ>второй — это статичный граф задач, через которые текут потоки сообщений. в обоих случаях понятно как распространять исключения. в общем-то, мне кажется и в sobj это можно сделать — если кто-то подписался на сообщения от скончавшегося актора, то вместо очередного сообщения возбуждаем исключение в получателе. вероятно, в вашей скада-системе это просто незачем, да и вообще в моих по крайней мере задачах publish-subscribe имеет смысл только для доп. функционала (ui, logfile), а граф управления выглядит иначе
Ну, вообще, самый большой плюс от future — это то, что они являются еще и монадами maybe {result, error}, с распространением исключений по всей связанной цепочке (в будущих расширениях, типа future::then). Да, на этом можно строить иерархии задач (графов агентов) с автоматическим распространением ошибки к некоему "корню".
Здравствуйте, vdimas, Вы писали:
V>Более того, насколько я понял его работу, необязательно использовать встроенные в SObjectizer ср-ва для генерирования состояний автоматов и логики хождения м/у ними, можно же не использовать эту часть фреймворка, т.е. с т.з. вреймворка у агента может быть только одно состояние, а всё остальное (реакция на входящие сообщения) можно реализовать ручками, не затрагивая инфраструктуру.
Типа того. Только более точно будет сказать так: можно обработку всех сообщений, в которых заинтересован агент, повесить на одно и то же состояние по-умолчанию, которое есть в каждом агенте. Кроме того, в SO-5 есть т.н. ad-hoc агенты, для которых не нужно определять свой собственный C++класс и инстанциировать его объекты. Можно заставить SO-5 сгенерировать "заглушку", на которую повесить сколько угодно обработчиков сообщений в виде лямбда-функций.
Ну и до кучи. В SO-5 есть возможность синхронного общения между агентами. Т.е. один агент запрашивает что-то у другого и получает future, на которой может ждать поступление ответа. Соответственно, если обработка запроса завершилась исключением, это исключение будет возвращено через future.