Здравствуйте, -MyXa-, Вы писали:
MX>Здравствуйте, Qbit86, Вы писали:
Q>>Здравствуйте, -MyXa-, Вы писали:
MX>>>А в С# корутины не годятся (они без стека) — весь код придётся переписывать.
Q>>Почему это не годятся? В C# есть и корутины через yield return, и async/await. Какого именно стека им не хватает?
MX>Ты-ж сам ответил на свой вопрос — потому, что надо расставить async и await в стратегических местах. А если Майкрософт её слово придумают — тоже будем его по всему коду расставлять?
Ты так говоришь, как будто это что-то плохое (С)
Все равно код с await будет непринужденно работать с синхронной реализацией. Какие проблемы написать два слова?
Социализм — это власть трудящихся и централизованная плановая экономика.
Здравствуйте, LaPerouse, Вы писали:
LP>С этим как бы никто и не спорит. Но если можно обойтись без состояния вообще, или с маленьким количеством состояний, а вместо этого городят большой автомат, то соответственно, такой автомат хуже, чем его отсутствие.
Конечно. Только непонятно какое это всё имеет отношение к модели акторов. Вот скажем подобный C++ код:
void OnClick()
{
thread([url=UrlCtrl.GetText()]{
PostMessage(DownloadData(url)); //PostMessage - функция из используемой gui библиотеки
}).detach();
}
void OnData(Data d)
{
OutputCtrl.SetText(d);
}
1. Это модель акторов или нет?
2. Используется по делу или нет?
3. Сложный и неудобный код или нет?
Да, конечно, я легко могу на том же C++ с помощью сахара сопрограмм преобразовать этот же код к чему-то вроде:
void OnClick() async
(
OutputCtrl.SetText(await([url=UrlCtrl.GetText()]{return DownloadData(url);})); //async и await - обёртки над сопрограммой/созданием потока
)
И он действительно будет внешне покороче. Но удобнее ли с помощью него понимать, что там действительно происходит?
Re[10]: Об очередном антипаттерне. Модель акторов.
_>Здравствуйте, LaPerouse, Вы писали:
LP>>С этим как бы никто и не спорит. Но если можно обойтись без состояния вообще, или с маленьким количеством состояний, а вместо этого городят большой автомат, то соответственно, такой автомат хуже, чем его отсутствие.
_>Конечно. Только непонятно какое это всё имеет отношение к модели акторов. Вот скажем подобный C++ код:
Какой-то странный у вас С++ код, я такой никогда не видел (лямбды, замыкания...). Я работаю все больше с легаси-кодом, который надо допилить для интеграции с нашим ява-софтом и видеть замыкания в С++ для меня не менее странно, чем айфон в пирамиде Хеопса.
_>[ccode] _>1. Это модель акторов или нет?
Нет
_>2. Используется по делу или нет?
Да
_>3. Сложный и неудобный код или нет?
Нет
_>И он действительно будет внешне покороче. Но удобнее ли с помощью него понимать, что там действительно происходит?
Одинаково понятно, если сделать скидку на странный С++. Также понятно, что приведенный пример никакого отношения не имеет к тому, о чем я писал.
Социализм — это власть трудящихся и централизованная плановая экономика.
Re[11]: Об очередном антипаттерне. Модель акторов.
Здравствуйте, LaPerouse, Вы писали:
_>>1. Это модель акторов или нет? LP>Нет
Почему? )
_>>И он действительно будет внешне покороче. Но удобнее ли с помощью него понимать, что там действительно происходит? LP>Одинаково понятно, если сделать скидку на странный С++.
Если одинаково, то может тогда и не стоит добавлять сопрограммы? ) Тогда тот же async/await из C# не имеет особого смысла? )
LP>Также понятно, что приведенный пример никакого отношения не имеет к тому, о чем я писал.
Хм, а разве есть какое-то принципиальное отличие этого моего примера и данного кода
actor = new Actor();
actor.sendMessage(new CalculateSomething(1, 2, 3));
someHardCalculation();
из пункта номер 1 изначального сообщения темки? ) Заменяем CalculateSomething на DownloadData, a someHardCalculation на "обработка следующих сообщений в очереди (GUI приложения)"...
Здравствуйте, LaPerouse, Вы писали:
MX>>Ты-ж сам ответил на свой вопрос — потому, что надо расставить async и await в стратегических местах. А если Майкрософт её слово придумают — тоже будем его по всему коду расставлять?
LP>Ты так говоришь, как будто это что-то плохое (С) LP>Все равно код с await будет непринужденно работать с синхронной реализацией. Какие проблемы написать два слова?
Какие были проблемы использовать ISequentialStream в C#, что понадобилось сделать монструозный (десять страниц одних оглавлений... ) System.IO.Stream?
Я думаю, и у твоего вопроса, и у моего один ответ. Но давай не будем о грустном. Зачем нам чужие проблемы?
З.Ы. Я в C# ничего не понимаю — есть ReadByte и ReadAsync. Где ReadByteAsync?
З.Ы.Ы. У них там ещё маленько legacy есть, вроде BeginRead. Т.е. концепцию "generic view of a sequence of bytes" не осилить в первой итерации.
Если не поможет, будем действовать током... 600 Вольт (C)
M>>В том же Erlang'е практически все описанные «проблемы» давно, по сути, решены и описываются во первых же строках любой книги по Erlang'у. Курить те же деревья супервизоров.
LP>Ну, начнем с того, что у меня не erlang, а jvm или c++. И как минимум там это действительно выглядит как антипаттерн.
Поэтому и первая часть моего сообщения Без поддержки среды это все быстро становится очень и очень печальным. Потому что, скажем «по процессу/потоку на пользователя» в Erlang'е рассматривается обычно на уровне туториалов, и проблем не вызывает
LP> Про erlang ничего сказать не могу, не читал, не использовал, не сталкивался. Но с эрлангом или без, от конечного автомата все равно не убежите. Другое дело, он может быть замаскирован корутинами или продожениями. Но в любом случае будет интересно увидеть, что же так сказать эрланг животворящий делает. Как будет выглядеть на эрланге приведенный мной пример (с Response1 и Response2)?
В простейшем случае используется поставляемый с Erlang'ом gen_server (по сути, определение интерфейса). Код, реализующий gen_server, должен реализовать, по сути, следующее (псевдокод):
Никаких AWAIT_RESPONSE, потому что это все уже реализовано внутри стандартного gen_server'а
— handle_call — реализация синхронного вызова
— handle_cast — реализация асинхронного вызова, обычно по принципу "Fire and forget"
В любом случае: init — это создание нового процесса/потока, в который отсылаются сообщения
MX>Какие были проблемы использовать ISequentialStream в C#, что понадобилось сделать монструозный (десять страниц одних оглавлений... ) System.IO.Stream?
Кэп: потому что Stream — это аналог IStream.
Кэп #2: таймауты, тек. позиция, seek, асинхронное чтение, тип потока (чтение/запись/оба), flush etc. Если этого нет из коробки, то на ровном месте придётся городить тонну костылей. Для каждого из мест использования.
И да, если для вас класс с парой десятков свойств-методов монструозный — вы точно разрабатываете под шарп в IDE? Если нет — не всё ли равно, что там у шарповодов?
MX>З.Ы. Я в C# ничего не понимаю — есть ReadByte и ReadAsync. Где ReadByteAsync?
А зачем? Хоть один сценарий для такого изврата есть?
Re[12]: Об очередном антипаттерне. Модель акторов.
Здравствуйте, alex_public, Вы писали:
_>Здравствуйте, LaPerouse, Вы писали:
_>>>1. Это модель акторов или нет? LP>>Нет
_>Почему? )
Потому что это callback. Я думал, это очевидно.
Callback отличается от актора тем, что он снабжен контекстом вызова. Response приходит в определенном контексте — в котором был послан request.
В случае актора reponse может прийти в ЛЮБОМ контексте. Он может прийти даже сам по себе, без всякого request, если другой актор послал это сообщение по какому-то событию,
хотя в данном случае Response не совсем правильное название для такого сообщения.
Поэтому нужен конечный автомат, чтобы разобрать состояния.
Callback:
if (state == 1)//В Callback я знаю, что state == 1, поэтому вызываю calc1
call(request1, response -> {calc1(response)});
else if (state == 2)//В Callback я знаю, что state == 2, поэтому вызываю calc2
call(request2, response -> {calc2(response)});
Actor:
if (state == 1) {
fsmState = WAIT_FOR_RESPONSE1;
call(request1);
}
else if (state == 2) {
fsmState = WAIT_FOR_RESPONSE2;
call(request2);
}
//Обработка
void messageReceived(Response response) {
if (fsmState == WAIT_FOR_RESPONSE1)
calc1(response);
else if (fsmState == WAIT_FOR_RESPONSE2)
calc2(response);
else if (fsmState == NO_REQUEST)//здесь сообщение Response пришло без Request
processEvent(response);
}
Актор можно переписать, сделав в какой-то степени похожим на колбак, то есть снадбив контекстом вызова, при помощи сопрограммы:
if (state == 1) {
response = await call(request1);
calc1(response);
}
else if (state == 2) {
response = await call(request2);
calc2(response);
}
//Обрабатываем самопроизвольный приход Response (хотя название Response для сообщения в данном случае не самое удачное)
void messageReceived(Response response) {
processEvent(response);
}
Если в языке нету поддержки сопрограмм, то приходится реализовывать актор при помощи явных конечных автоматов. Если бы ты внимательно прочитал вторую часть моего соообщения, этого обсуждения не было бы.
_>Хм, а разве есть какое-то принципиальное отличие этого моего примера и данного кода _>из пункта номер 1 изначального сообщения темки? ) Заменяем CalculateSomething на DownloadData, a someHardCalculation на "обработка следующих сообщений в очереди (GUI приложения)"...
Если бы ты взял не первый пример (который как раз демонстрировал НЕПРАВИЛЬНОЕ использование актора), а последний, ты бы не написал это сообщение.
Социализм — это власть трудящихся и централизованная плановая экономика.
Здравствуйте, Mamut, Вы писали:
M>>>В том же Erlang'е практически все описанные «проблемы» давно, по сути, решены и описываются во первых же строках любой книги по Erlang'у. Курить те же деревья супервизоров.
LP>>Ну, начнем с того, что у меня не erlang, а jvm или c++. И как минимум там это действительно выглядит как антипаттерн.
M>Handle = some_module.init()
M>Response1 = some_module.message1(Handle, Data1) M>Response2 = some_module.message2(Handle, Data2) M>Response3 = some_module.message3(Handle, Data3) M>[/code]
M>Никаких AWAIT_RESPONSE, потому что это все уже реализовано внутри стандартного gen_server'а M>— handle_call — реализация синхронного вызова M>— handle_cast — реализация асинхронного вызова, обычно по принципу "Fire and forget" M>В любом случае: init — это создание нового процесса/потока, в который отсылаются сообщения
Узнаю, я сделал похожее в одном легасе акторе, чтобы уменьшить количество состояний.
Коротко — актор делал ряд запросов к другому актору, который присылал сообщение в ответ. Но это сообщение приходило не только в ответ на _разные_запросы, но еще и само по себе. Я сделал callback и снадбил каждую отправку сообщений уникальным идентификатором. Если ответ пришел в ответ на сообщение, посланное через callback, вызывался callback. Если без callback, вызывался обычный receive. Правда, в отличие от эрланга, у меня вызов был не синхронным и не блокирующим (callback). То, что я увидел в приведенном выше коде — это обычный блокирующий вызов. Даже не касаясь того, что делать блокирующий вызов в акторе сродни безумию (может мудрый эрланг это как-то обходит), задам другой вопрос — смысл тогда использовать актор, если во время ожидания запроса актор заблокирован и не может обработать другие сообщения?
Социализм — это власть трудящихся и централизованная плановая экономика.
LP>Даже не касаясь того, что делать блокирующий вызов в акторе сродни безумию (может мудрый эрланг это как-то обходит), задам другой вопрос — смысл тогда использовать актор, если во время ожидания запроса актор заблокирован и не может обработать другие сообщения?
Тогда можно делать не call (блокирующий вызов), а cast (асинхронный вызов).
Если мы ожидаем ответа от асинхронного вызова, то нам нужно ожидать получение этого ответа. Для этого в Эрланге есть прекрасная конструкция receive. Да, это примерно соответсвует wait_for_что_то_там из приведенного тобой примера.
some_function() ->
async1(),
asyn2(),
asyn3(),
wait_for_response([]). %% если мы не хотим блокировать
%% выполнение, и эту функцию тоже
%% можно вынести в отдельный процесс/поток
wait_for_response(ResponseAccum) when length(ResponseAccum) == 3->
ResponseAccum; %% возвращаем аккумулированные ответы
wait_for_response(ResponseAccum) ->
%% при получении ответа просто добавляем ответ в список ответов
%% и продолжаем ждать. tail call optmization
%% позволяет делать рекурсивный вызов сколько угодно раз
%% receive выбирает из сообщений, поступающих в процесс,
%% те, что соответсвуют паттерну. Ожидание не стоит ничего.
%% Вплоть до того, что процесс могут просто выключить из очереди
%% на обработку до тех пор, пока к нему не придет сообщение
receive
{response1, Data} -> wait_for_response(ResponseAccum ++ Data)
{response2, Data} -> wait_for_response(ResponseAccum ++ Data)
{response3, Data} -> wait_for_response(ResponseAccum ++ Data)
%% можно опционально добавить таймаут, и не один, если надо
after
1000 -> log("все ждем ответа");
10000 ->
log("ответа не дождались, аборт"),
error("Aborting")
end.
На синтетических примерах это плохо видно, но на практике обычно все это нанизывается на дерево супервизоров, которое следит: ага, тут у нас аварийное завершение, нужно ли перезапустить, или не нужно, или надо убить всю ветку исполнителей и перезапустить ее заново, или убиться самому, или... или...
Плюс сами процессы стоят копейки (зеленые потоки), стоимость их порождения можно увидеть может быть на каких-то сверхнагруженных системах. И то... Весь boilerplate по их управлению (даже выписаные мной receive ... end) обычно давно упаковаан в стандартные библиотеки, решающие практические проблемы: (gen — generic) gen_server, gen_supervisor, gen_fsm (да, даже машина состояний, если вдруг надо будет ).
Поэтому решение задач выглядит обычно как «ага, нам нужно пяток исполнителей, собранных в одну группу, к ним будет обращаться вот эта гроздь клиентов, которая динамически изменяется, их lifecycle будет отслеживать (автоматически, замечу) вот этот контролирующий процесс». Все это нанизывается друг на друга, и программирование с их использованием редко отличается от программирования в стиле
X = f(),
Y = g(),
Z = h(X, Y).
несмотря на то, что на фоне — полтора миллиона процессов и маленькая тележка
Здравствуйте, LaPerouse, Вы писали:
LP>Ну, начнем с того, что у меня не erlang, а jvm или c++. И как минимум там это действительно выглядит как антипаттерн. Про erlang ничего сказать не могу, не читал, не использовал, не сталкивался. Но с эрлангом или без, от конечного автомата все равно не убежите. Другое дело, он может быть замаскирован корутинами или продожениями.
а причём тут конечные автоматы? если ты про реализацию, то конечные автоматы нужны только для stackless coroutines. в осталльных случаях просто переходит переключение стёков, зелёные потоки в эрланге отличаются от stackful coroiutines в C только тем, что VM обеспечивает автоматическое переключение потоков с какой-то периодичностью, например в процессе выделения памяти
Здравствуйте, Mamut, Вы писали:
M>Практически все описаные антипаттерны возникают из отсутсвия в языке и среде нативной поддержки собственно модели акторов.
из твоих слов получается что акторы являются антипаттерном в любом языке кроме эрланга
Здравствуйте, BulatZiganshin, Вы писали:
BZ>Здравствуйте, LaPerouse, Вы писали:
LP>>Ну, начнем с того, что у меня не erlang, а jvm или c++. И как минимум там это действительно выглядит как антипаттерн. Про erlang ничего сказать не могу, не читал, не использовал, не сталкивался. Но с эрлангом или без, от конечного автомата все равно не убежите. Другое дело, он может быть замаскирован корутинами или продожениями.
BZ>а причём тут конечные автоматы?
Если нет coroutines, то нужно либо писать автомат, либо делать синхронный вызов с блокировкой, либо делать хитрый callback как описано здесь: http://rsdn.ru/forum/philosophy/6157243.1
BZ>если ты про реализацию, то конечные автоматы нужны только для stackless coroutines. в осталльных случаях просто переходит переключение стёков, зелёные потоки в эрланге отличаются от stackful coroiutines в C только тем, что VM обеспечивает автоматическое переключение потоков с какой-то периодичностью, например в процессе выделения памяти
Это-то понятно. Значит, все таки coroutines. Что и требовалось доказать.
Социализм — это власть трудящихся и централизованная плановая экономика.
LP>>Даже не касаясь того, что делать блокирующий вызов в акторе сродни безумию (может мудрый эрланг это как-то обходит), задам другой вопрос — смысл тогда использовать актор, если во время ожидания запроса актор заблокирован и не может обработать другие сообщения?
M>Тогда можно делать не call (блокирующий вызов), а cast (асинхронный вызов).
Это-то понятно. Но пример в исходном сообщении как раз показывает, что обработка ответа на асинхронный вызов может быть сложной, так как в нетривиальном случае обработка зависит от состояния.
M>Если мы ожидаем ответа от асинхронного вызова, то нам нужно ожидать получение этого ответа. Для этого в Эрланге есть прекрасная конструкция receive. Да, это примерно соответсвует wait_for_что_то_там из приведенного тобой примера.
Да, и в том примере если ты заметил весьма запутанный код, который сильно сложнее синхронного случая. Его можно облегчить, по сути убрать явный автомат при помощи coroutines. Это например если сообщение, которое пришло в ответ на cast, приходит не в receive, а прямо в ответ на cast, но вызов при этом не блокирующий
resp = await cast(actor, msg);//происходит возврат управления из функции, актор готов принимать сообщения
//Отсюда выполнение продолжится, когда придет ответ на запрос
process(resp);
Таким образом все может быть сведено к этому:
M>Все это нанизывается друг на друга, и программирование с их использованием редко отличается от программирования в стиле M>
M>X = f(),
M>Y = g(),
M>Z = h(X, Y).
M>
Социализм — это власть трудящихся и централизованная плановая экономика.
Здравствуйте, LaPerouse, Вы писали:
LP>>>Ну, начнем с того, что у меня не erlang, а jvm или c++. И как минимум там это действительно выглядит как антипаттерн. Про erlang ничего сказать не могу, не читал, не использовал, не сталкивался. Но с эрлангом или без, от конечного автомата все равно не убежите. Другое дело, он может быть замаскирован корутинами или продожениями.
BZ>>а причём тут конечные автоматы?
LP>Если нет coroutines, то нужно либо писать автомат,
вот тут у тебя лог. ошибка. из того, что в отсутствии короутин нужно делать автомат — не следует что все короутины реализуются только через автоматы. считай что это обычные потоки для начала, они ведь без автоматов обходятся?
LP>Это-то понятно. Значит, все таки coroutines. Что и требовалось доказать.
Здравствуйте, BulatZiganshin, Вы писали:
BZ>Здравствуйте, Mamut, Вы писали:
M>>Практически все описаные антипаттерны возникают из отсутсвия в языке и среде нативной поддержки собственно модели акторов.
BZ>из твоих слов получается что акторы являются антипаттерном в любом языке кроме эрланга
Если есть хотя бы yield return null, то писать их становится сильно проще. Если их нет (java), то просто беда.
Социализм — это власть трудящихся и централизованная плановая экономика.
Здравствуйте, BulatZiganshin, Вы писали:
BZ>вот тут у тебя лог. ошибка. из того, что в отсутствии короутин нужно делать автомат — не следует что все короутины реализуются только через автоматы
А кто так считает-то? И причем тут вообще стековые корутины?
Ты неправильно прочитал. Я нигде не писал, что для корутин нужно писать конечный автомат (хотя для безстековых нужно, но это особенность реализации). Но в отсутствии корутин нужно писать конечный автомат.
LP>>Это-то понятно. Значит, все таки coroutines. Что и требовалось доказать. BZ>не coroutines, а green threads
Как без корутин сделать такой код
resp = await cast(actor, msg);//происходит возврат управления из функции, актор готов принимать сообщения
//Отсюда выполнение продолжится, когда придет ответ на запрос
process(resp);
Социализм — это власть трудящихся и централизованная плановая экономика.
Здравствуйте, LaPerouse, Вы писали:
LP>Ты неправильно прочитал. Я нигде не писал, что для корутин нужно писать конечный автомат (хотя для безстековых нужно, но это особенность реализации). Но в отсутствии корутин нужно писать конечный автомат.
это твои слова или нет: "Но с эрлангом или без, от конечного автомата все равно не убежите. Другое дело, он может быть замаскирован корутинами или продожениями." ?
Здравствуйте, BulatZiganshin, Вы писали:
BZ>Здравствуйте, LaPerouse, Вы писали:
LP>>Ты неправильно прочитал. Я нигде не писал, что для корутин нужно писать конечный автомат (хотя для безстековых нужно, но это особенность реализации). Но в отсутствии корутин нужно писать конечный автомат.
BZ>это твои слова или нет: "Но с эрлангом или без, от конечного автомата все равно не убежите. Другое дело, он может быть замаскирован корутинами или продожениями." ?
Ну стековые корутины это вообще экзотика. При слове корутины все представляют себе yeild return иил async/await. Так что следовало читать это сообщение так:
"Но с эрлангом или без, от конечного автомата все равно не убежите. Другое дело, он может быть замаскирован безстековыми корутинами или продожениями." ?
Социализм — это власть трудящихся и централизованная плановая экономика.