Здравствуйте, vdimas, Вы писали:
V>Про это тоже идут споры не один десяток лет. TCP для HTTP — это для 99% сценариев из пушки по воробьям. Давно уже высказывались мысли, что для HTTP больше подойдет message-oriented протокол, но, в отличие от UDP, чтобы гарантировалась доставка.
Если бы это было серьёзной проблемой, давно бы в винде встроили SCTP из коробки.
Здравствуйте, Ikemefula, Вы писали:
I>Кстати, вопрос такой — ты в курсе, что Windows до определенной версии поддерживала только кооперетивную многозадачность ? I>Ты в курсе, что уже тогда в ней были и семафоры, и муткесы. I>Как так, ведь в кооперативной многозадачности "гонок нет" I>Ась ?
На этом месте я перестал понимать, что же ты доказываешь.
Твой пример, с которым ты влез в эту подветку, относился к ситуации примерно следующего вида:
1. Действие A заказывает две внешние операции, для первой коллбэк B, для второй C.
2. C (когда оно вызовется) сложит ответ в некоторый компонент глобального состояния.
3. B вызывается со своим ответом и ожидает, что уже есть ответ от C. Но его нет, поэтому B получает чушь.
4. C вызвался после B, но чушь уже состоялась.
Если это так, это действительно проблема, с которой борются своими методами. Например, для JS это может быть отработано так. И в B, и в C как в замыкания передаётся объект (не обязательно глобальный!) R, по ссылке. B кладёт в него свой результат, C — свой, но оба проверяют: если уже есть оба ответа, они немедленно вызывают финальную обработку суммарного результата.
И это:
* не связано ни с какой глобальностью (более того, она зависит от того, сколько таких операций может быть впараллель — если больше одной, R не может быть глобальным)
* не имеет никакого отношения к мьютексам/etc.
* точно так же может быть сделано и на той же Win16, без привлечения мьютексов; только надо уточнить методы получения завершения внешней операции;
* точно так же, и на Win16, и на node.js, и на чём угодно, что умеет звать коллбэки, может быть написано правильно, а может — нет (если B не проверяет, завершилось ли C), и если нет — получим обгон "в полный рост".
Ну а мьютексы на Win16 были нужны для того, чтобы обеспечить доступ при длительной операции, которая сама по себе распадается на несколько потенциально блокирующих (соответственно — точки переключения шедулера). Но не все же операции так пишутся, а vdimas тебе приводит примеры вида ++i, где точно нет такой точки переключения
I>У тебя вытесняющая многозадачность, а я говорю про кооперативную. Представь себе, гонки это свойство любой многозадачности, а не только вытесняющей.
Да, но при этом уровни их проявления совсем разные.
Если у тебя нет принудительного переключения, i++ пройдёт гладко.
Если ты можешь его приостанавливать (splxxx() в классике Unix), аналогично.
(Везде предполагаем одно ядро CPU.)
А если у тебя в операции сотня чтений из сети, записей, работ с файлами и т.п., то тебе нужен менеджер транзакций с управляемым уровнем изоляции
[[skip банальности]] I>И видим — в обоих случах порядок выполнения недетерминирован, потому что определяется не твоим кодом, а хрен знает сколькими внешними факторами.
Верно. Остался один вопрос — нафига так делать? И какие реальные ситуации заставили тебя рисовать такой пример?
Поскипаное выше внимательно прочитано. Очень сильно совпадает с опытом, полученным при эксплуатации разработанных на базе SObjectizer систем.
N>>>Большие объёмы данных, много параллельных запросов и плотные потоки — это три разных случая, обычно не сильно пересекающиеся. Свидетель описывает случай 3, который на штатном рантайме, считаем, нереализуем. Случай 2 — самый подходящий. 1 — зависит от подробностей. S>>Отрадно, когда в треде появляются люди, понимающие что к чему S>>ИМХО, со вторым случаем, т.е. с потоком параллельных запросов, так же не все однозначно. Можно создавать по воркеру на каждый запрос. А можно иметь отдельный воркер на ядро, а запросы представлять внутри воркера в виде конечных автоматов. И у того, и у другого подхода есть свои достоинства и недостатки.
N>В случае Erlang обычно достаточно по воркеру на запрос. Это проще всем, потому что такой воркер отработал и застрелился, не требуется никакой сложной зачистки за ним. Если же это выводить в пул автоматов, как минимум уже утяжеление, что GC должен реально отработать, а не прихлопнуть целиком всю память застрелившегося процесса. Ну и сами автоматы писать достаточно громоздко. Нам в Clustrx пришлось идти по пути автоматов, потому что надо было хранить состояние объекта со множеством подробностей между оповещениями от него, и я помню неудобства, связанные с этим.
Когда есть воркер, а активности представлены хранящимися в воркере КА, появляются возможности для более простого мониторинга всего этого дела (точно известно, сколько активностей, можно даже знать сколько в каком состоянии) и для дозированной работы над активностями.
Пусть, например, каждая активность -- это отдельный процесс (нить, короутина). Может случиться так, что у нас в результате какого-то всплеска образовалось 100K активностей и практически все они должны быть убиты по тайм-ауту. Если каждая активность — это отдельная сущность, которая сама взводит таймер, то можно ожидать затем всплеска таймеров и, соответственно, большого расхода ресурсов на обработку этих таймеров. Аналогичная проблема возникает, когда активности периодически должны повторять свои действия (отослали запрос в стороннюю систему, не получили ответ за 10 секунд, отослали запрос повторно, опять заснули и так несколько раз). Здесь так же можно ожидать всплесков таймеров.
Если активность -- это KA внутри воркера, то достаточно нескольких таймеров, при срабатывании которых проверяется, есть ли активности, подлежашие удалению из-за истечения срока жизни. Если есть, то убиваются N из них. Затем можно проверить, есть ли активности, для которых нужно повторение чего-либо (запроса в стороннюю систему) и обслуживается K из них. И т.д. и т.п. При этом можно контролировать времена, затраченные на каждом из шагов и, в зависимости от них, выбирать значения N, K и т.д.
Все это пересекается с одним из пунктов, описанных вами выше: "Допускается постоянно живущих в объёме M*Ncores, где M — достаточно малая константа. Допускается огромное количество (грубо говоря, миллионы) процессов, но чтобы каждый из них жил одну операцию (самое крупное — ответ на http запрос, или транзакция), не постоянно, и чем короче — тем лучше (нижняя граница срока жизни — определяется моментом, когда переброс данных между такими эфемерами становится слишком дорогим)." Как раз воркеры с представлением активностей в виде KA являются способом решения проблемы больших значнией M. Но вы об этом, по-моему, сами же и сказали.
Здравствуйте, netch80, Вы писали:
N>Если за последние пару лет ничего не изменилось, они не позволяют плавный переход на новый код
Зависит от архитектуры системы и того что ты вкладываешь в термин "плавное". Разумеется, ограничений существенно больше, но в большинстве случаев это процессу разработки не особо мешает. И уж обновить прикладную логику сервиса не сбрасывая сессию клиента, как в описанном примере, вполне возможно.
Здравствуйте, netch80, Вы писали:
N>В существенной мере и поэтому.
серьёзно?
а если забыть на время о динамике, то какая технология становится альтернативой энларгу?
N>Потому, что любой интерфейс можно расширить, например, через параметр options/flags/whatever, передавая его как proplist каких-то уточнений с любыми типами данных (на статически типизированных языках это потребует извращений вроде Variant
это потребует заведения нового типа. не так уж и страшно.
Здравствуйте, vdimas, Вы писали:
EP>>Никакое не ЧТД EP>>Что мешает вот это: EP>>
EP>>future<void> coroutine() ...
Преобразовать вот в это: EP>>
EP>>struct coroutine : asio::coroutine ...
EP>>
? V>То, что future<void> — это value-type, который владеет через shared над future_shared_state.
И как это мешает?
V>>>Так вот, если уши торчат, как в твоём примере, то нафига это тянуть в стандарт? EP>>Я не предлагаю в стандарт тянуть эмуляцию на макросах. Ещё раз, я показываю как оно может быть устроенно внутри. V>Я прекрасно понимаю, как оно устроено изнутри. Но я, хоть убей, не могу понять, как ты собрался реализовать свои желания с т.з. системы типов C++? V>Вот у нас есть тип std::future, который устроен примерно так: V>
V>Т.е. внутри него всего одно поле и sizeof(future<>) скорее всего совпадет с sizeof(shared_state_). V>Где ты предлагаешь разместить аналог своего asio::coroutine в future<>?
Там же где и обычное обычное замыкание-продолжение.
Абстрагируйся от asio::coroutine, представь простое замыкание-продолжение. Например у нас вместо:
future<int> sum()
{
int x = await foo();
int y = await bar();
return x + y;
}
Где по-твоему здесь размещаются эти продолжения? И почему stackless корутина должна размещаться в каком-то другом месте?
V>Я бы его разместил в объекте-наследнике того самого shared_state, который, в свою очередь, создаётся на куче.
А здесь можно вообще не менять ни сам future, ни лезть в его внутренности (типа наследования от shared_state). Потому что используем ли мы обычные продолжения, или объект-автомат (ручной или сгенерированный stackless coroutine) — это совершенно ортогонально тому где мы их размещаем.
V>Другое дело: V>
V>Т.к. тип iterator<> еще не специфирован, вполне возможно, что у него будет некое зарезервированное поле, в котором можно поместить целочисленную переменную — состояние автомата-корутины.
Ты понимаешь что над корутинами из Boost.Asio можно сделать обёртку-итератор, в котором полностью будет хранится вся корутина? Или показать пример?
V>Если твоя легковесная корутина будет содержать в себе другие корутины, то нужно эмулировать стек для их правильного обхода. И тут уже, извини, но без выделения памяти в куче — никак.
Зачем? Вложенная корутина будет просто полем объекта внешней корутины
Вот пример, в нём нет никаких аллокаций в куче (Live Demo on Coliru):
Здравствуйте, vdimas, Вы писали:
V>Вдогонку. Что насчет локальных переменных корутины? Где их хранить?
Так в этом же вся суть
Функция автоматически преобразовывается в класс-автомат, и её локальные переменные становятся полями класса.
Тоже самое например происходит в показанном ранее примере C# — обрати внимание на исходный код с await, на переменную result, и на поле <result>5__1 в сгенерированном коде
V>В примерах буста они хранятся в структуре-наследнике asio::coroutine. V>А как будет выглядеть аналогичный объект с т.з. системы типов С++ в случае: V>
V>Где будет хранится локальная переменная tmp в твоём случае, т.е. когда iterator<> — это полностью value-type объект?
Она будет хранится внутри объекта-автомата, который будет хранится внутри самого итератора. Естественно тип итератора будет зависеть от типа класса-автомата.
Здравствуйте, neFormal, Вы писали:
N>>В существенной мере и поэтому. F>серьёзно? F>а если забыть на время о динамике, то какая технология становится альтернативой энларгу?
Не знаю, не разбирался. Но полный комплекс описанных фич вроде бы не представлен нигде.
N>>Потому, что любой интерфейс можно расширить, например, через параметр options/flags/whatever, передавая его как proplist каких-то уточнений с любыми типами данных (на статически типизированных языках это потребует извращений вроде Variant F>это потребует заведения нового типа. не так уж и страшно.
Один раз не страшно, но на каждый раз, плюс менять и подтачивать интерфейсы... в конце концов или получается дерево тех же Variant, или полный бардак в процессе разработки.
Здравствуйте, Ночной Смотрящий, Вы писали:
НС>Здравствуйте, netch80, Вы писали:
N>>Если за последние пару лет ничего не изменилось, они не позволяют плавный переход на новый код НС>Зависит от архитектуры системы и того что ты вкладываешь в термин "плавное".
Без разрыва соединений, без замораживания всех одновременно до момента перехода, с минимальной задержкой на сам переход (необходимые конверсии структур плюс небольшие накладные расходы на процесс перехода).
НС> Разумеется, ограничений существенно больше, но в большинстве случаев это процессу разработки не особо мешает. И уж обновить прикладную логику сервиса не сбрасывая сессию клиента, как в описанном примере, вполне возможно.
То есть все замораживаются, состояния сериализуются, выгружается код, загружается новый, состояния десериализуются и всё запускается опять? Или можно сократить цепочку?
Здравствуйте, netch80, Вы писали:
N>На этом месте я перестал понимать, что же ты доказываешь. N>Твой пример, с которым ты влез в эту подветку, относился к ситуации примерно следующего вида: N>1. Действие A заказывает две внешние операции, для первой коллбэк B, для второй C. N>2. C (когда оно вызовется) сложит ответ в некоторый компонент глобального состояния. N>3. B вызывается со своим ответом и ожидает, что уже есть ответ от C. Но его нет, поэтому B получает чушь. N>4. C вызвался после B, но чушь уже состоялась.
Нет, не про это. Есть логическая задача, которая работает с глобальным ресурсом. Она асинхронная и довольно длинная. Нужно запустить параллельно еще несколько таких же задач при сохранении всех инвариантов.
N>Если это так, это действительно проблема, с которой борются своими методами. Например, для JS это может быть отработано так. И в B, и в C как в замыкания передаётся объект (не обязательно глобальный!) R, по ссылке. B кладёт в него свой результат, C — свой, но оба проверяют: если уже есть оба ответа, они немедленно вызывают финальную обработку суммарного результата.
Как ты передашь базу данных или файл в замыкании ?
N>Ну а мьютексы на Win16 были нужны для того, чтобы обеспечить доступ при длительной операции, которая сама по себе распадается на несколько потенциально блокирующих (соответственно — точки переключения шедулера).
Вот про это я и говорю.
>Но не все же операции так пишутся, а vdimas тебе приводит примеры вида ++i, где точно нет такой точки переключения
Да, он постоянно спрыгивает совсем на другую тему. Собтсвенно многозадачность в джаваскрипте ровно мало чем отличается от многозадачности в Win16. Там переключения были унутре длинных блокирующих вызовов, а в JS в основном все иначе — апи неблокирующий, т.е. ты сам должен явно связывать цепочки руками.
I>>У тебя вытесняющая многозадачность, а я говорю про кооперативную. Представь себе, гонки это свойство любой многозадачности, а не только вытесняющей.
N>Да, но при этом уровни их проявления совсем разные.
Это непринципиально. Возьми скажем файл или бд в качестве ресурса и получишь ровно одни те же проблемы в обоих случаях из за недетерминированого доступа к ресурсу.
Т.е. снаружи ты даже не сможешь отличить одно от другого.
I>>И видим — в обоих случах порядок выполнения недетерминирован, потому что определяется не твоим кодом, а хрен знает сколькими внешними факторами.
N>Верно. Остался один вопрос — нафига так делать? И какие реальные ситуации заставили тебя рисовать такой пример?
Есть скажем кусок линейного кода в JS, который отлажен, оттестирован. Теперь, внезапно, эта задача может запускаться в нескольких экземплярах работающих параллельно. Наиболее дешовый способ это оставить логику, как есть, только выровнять доступ к глобальному ресурсу.
В простых случаях этого более чем достаточно. В более сложных придется пилить чтото навроде агентов, и здесь, что характерно, снова все вокруг глобального ресурса.
Здравствуйте, Ночной Смотрящий, Вы писали:
_>>В разработке очень удобно, например, у нас ребята часто работают парами, серверный программист и клиентский (например, Unity3d). Когда клиентский в игровой сессии тестирует новую функциональность или ловит баг, серверный может поправить код и нажать Ctrl+S, и это сразу отразится на том, что видит клиентский, ему не нужно даже игровую сессию перезапускать, перелогиниваться. С С++ можно пойти чаю попить, пока перекомпилируется все, пока заново в игру зайдешь, пока проблему воссоздашь. НС>Но ведь С++ то не единственная альтернатива. Java или дотнет и компилируются секунды, и прекрасно позволяют перезагружать классы/сборки на ходу.
В C++ можно автоматом переходить на новые версии классов через загрузку версированных фабрик из динамических библиотек, прямо на живой системе. Как-то даже приходилось с таким работать.
Естественно заменить можно только то, о чём заранее позаботились, и только в специально предусмотренные моменты. Впрочем думаю и на .NET/Java будет намного больше оговорок чем в динамических языках.
Здравствуйте, vdimas, Вы писали:
I>>Ужос, а еще недавно ты говорил "Но уже есть готовые реализации. Дело в их популяризации." А теперь уже "пусть компилятор разгребается"
V>Конечно ужос, путать stackless-корутину со stackfull. Для второй давно всё есть и в ней можно делать yield на любом уровне вложенности.
Т.е. для более сложного механизма, большего масштаба и тд помощь компилятора уже не нужна ? Забавно
Здравствуйте, netch80, Вы писали:
N>Один раз не страшно, но на каждый раз, плюс менять и подтачивать интерфейсы... в конце концов или получается дерево тех же Variant, или полный бардак в процессе разработки.
по-моему, декларирование опциональных параметров — это наоборот упорядочивание хаоса.
напротив, опции, известные только внутри ф-ции, создают намного больше неопределённости.
Здравствуйте, neFormal, Вы писали:
F>Здравствуйте, netch80, Вы писали:
N>>Один раз не страшно, но на каждый раз, плюс менять и подтачивать интерфейсы... в конце концов или получается дерево тех же Variant, или полный бардак в процессе разработки.
F>по-моему, декларирование опциональных параметров — это наоборот упорядочивание хаоса.
Размечтался Хаос ты не упорядочишь. А вот ввести излишнюю жёсткость там, где её просто рано вводить — запросто.
F>напротив, опции, известные только внутри ф-ции, создают намного больше неопределённости.
Они не "известные только внутри" при этом, они нормально документированы в doxygen/etc.
V>>>Э, нет. Не банально. Очередь специфическая, решает проблему ABA весьма остроумным способом. Скажу так, на этом сайте, когда шло активное обсуждение lock-free алгоритмов, этот способ никем не упоминался.
НС>>Какой способ? Локализация данных по потокам с целью уменьшения блокировок? Довольно стандартный прием.
V>Речь об ABA-проблеме в случае lock-free стека. Описана здесь.
V>Решается вот так: V>\Program Files (x86)\Microsoft Visual Studio 10.0\VC\crt\src\collections.h V>
V> //
V> // An implementation of interlocked SLIST that does not support Pop. This
V> // avoids the ABA problem. The reason for this data structure is to get
V> // to the top node (Windows SLIST does not provide this functionality
V> // without FirstSListEntry).
V> // Type T is required to have an intrusive SLIST_ENTRY m_slNext.
V> //
V> template <class T>
V> class LockFreePushStack
V>
V>Скажу честно, обнаружив это с выходом VS2010 малость обрадовался, т.к. своё аналогичное решение было полностью умозрительным и нигде я его обсуждений не видел. Т.е. был уверен в нем, скажем, на 99.5%, не более. ))
Че-то мне кажется, что эта очередь lockfree для push, а когда дело дойдет до вытаскивания из нее данных, то lockfree превращается в тыкву и всему приходит полный SpinLock.
Здравствуйте, meadow_meal, Вы писали:
_>Selective receive нужен: _>1) для коллов
Именно.
_>для 1) — да, изменилось, в 2010: _>http://www.erlang.org/download/otp_src_R14A.readme _>см. OTP-8623 _>Идея в том, что если мы создаем уникальный терм и отправляем его получателю а затем далее в этой же функции выполняем selective receive по образцу содержащему этот терм, то компилятор делает специальную оптимизацию и стоимость всегда константна. Это может и хак, но во-первых он спрятан в недрах стандартной библиотеки (gen:call), а во-вторых решает практическую задачу (а Эрланг, как промышленный язык, ориентирован в первую очередь на практику)
В этом году и на R17 наткнулся на торможения в prim_inet:send(), ибо
try erlang:port_command(S, Data, OptList) of
[...]
true ->
receive
{inet_reply,S,Status} ->
?DBG_FORMAT("prim_inet:send() -> ~p~n", [Status]),
Status
end
что, надо было заставить тут передавать в драйвер и обратно "уникальный терм", чтобы это вдруг подпало под оптимизацию?
А в результате — квадратичные тормоза от длины очереди.
_>До 2010 года существовала как минимум реализация gen_server2 (сторонняя библиотека), обходящая эту проблему.
Это которая высасывала всю очередь в память процесса? Ну так она только вместо gen_server и не решала тормоза от того, что нападало после dispatch_msg, но до ответа на call. А могло быть ой много.
_>для 2) — не имеет значения, так как N всегда мало. _>Не знаю, есть ли у эрланга фундаментальные проблемы, но selective receive точно не одна из них.
Здравствуйте, netch80, Вы писали:
N>То есть все замораживаются, состояния сериализуются, выгружается код, загружается новый, состояния десериализуются и всё запускается опять?
Состояние то зачем сериализовать? Сейчас как то стараются без состояния в памяти между вызовами обходиться. И если оно все же нужно, то хранят его в сессии, а сессия связана с системными сборками, которые перегружать не надо. Поэтому перегрузка прикладной сборки состояние не затрагивает и никаких сериализаций не нужно.
Здравствуйте, Evgeny.Panasyuk, Вы писали:
EP>В C++ можно автоматом переходить на новые версии классов через загрузку версированных фабрик из динамических библиотек, прямо на живой системе. Как-то даже приходилось с таким работать.
В С++ для этого нужно специально и немало стараться, а в управляемых средах это доступно искаропки.
EP>Впрочем думаю и на .NET/Java будет намного больше оговорок чем в динамических языках.
Разумеется. По большому счету главная оговорка — экземпляры классов нужно из памяти все выгрузить. Но для веб- или REST приложений, где оно больше всего востребовано, оно и так уже такое. Т.е. экземпляры чисто прикладных классов между запросами как правило не живут. Так что правишь страничку и следующий же рефреш показывает новый вариант, без сброса приложения и сессии. А вот если ты какой нибудь global.asax потрогал, тут да, приложение автоматично рестартует.
Здравствуйте, Ночной Смотрящий, Вы писали:
N>>То есть все замораживаются, состояния сериализуются, выгружается код, загружается новый, состояния десериализуются и всё запускается опять?
НС>Состояние то зачем сериализовать? Сейчас как то стараются без состояния в памяти между вызовами обходиться. И если оно все же нужно, то хранят его в сессии, а сессия связана с системными сборками, которые перегружать не надо. Поэтому перегрузка прикладной сборки состояние не затрагивает и никаких сериализаций не нужно.
Это тебе с HTTP такая радость? Ну ну. У меня то SIP, то FIX, то RPC по Redis+ZeroMQ...
никакие "системные сборки" такого не поддержат
Здравствуйте, Ночной Смотрящий, Вы писали:
EP>>В C++ можно автоматом переходить на новые версии классов через загрузку версированных фабрик из динамических библиотек, прямо на живой системе. Как-то даже приходилось с таким работать. НС>В С++ для этого нужно специально и немало стараться, а в управляемых средах это доступно искаропки.
Там основная работа делается только один раз, в виде библиотеки.
EP>>Впрочем думаю и на .NET/Java будет намного больше оговорок чем в динамических языках. НС>Разумеется. По большому счету главная оговорка — экземпляры классов нужно из памяти все выгрузить.
Плюс сериализовать и десериализовать.
НС>Но для веб- или REST приложений, где оно больше всего востребовано, оно и так уже такое. Т.е. экземпляры чисто прикладных классов между запросами как правило не живут. Так что правишь страничку и следующий же рефреш показывает новый вариант, без сброса приложения и сессии.
Для такого примитивного сценария подойдёт даже простая замена исполняемого файла / "cgi" скрипта / и т.п. Такое и в C++ есть "искаропки"
Выше же речь шла про онлайн игры, а в них общий игровой мир живёт "сам по себе".