Re[24]: Good practice по алгоритмизации в условиях асинхронн
От: sergunok  
Дата: 25.08.10 12:05
Оценка:
Здравствуйте, gandjustas, Вы писали:

G>Здравствуйте, sergunok, Вы писали:



S>>>>Каким он может быть при условии, что снятие заявки до ее исполнения необходимо для некоторых стратегий?

G>>>1)Что вернет RegisterOrder в случае отмены операции?
S>>Canceled
G>То есть и RegisterOrder вернет Canceled, CancelOrder вернет Canceled?
G>Реализуешь такое?
Почему бы и нет.
И RegisterOrder и CancelOrder — асинхронные операции, причина завершения каждой из них не обязательно единственна.

G>>>2)Что вернет CancelOrder если заявка успеет реализоваться?

S>>Matched
G>Аналогично выше.

G>>>Ты с такими методами не сможешь псевдосинхронно записать действия хотя их последовательность линейна. Тебе понадобится тогда джоинить потоки событий (простыни кода), а не писать маленькие Linq запросы.

S>>Да, понимаю.. Но не очень вижу путь.
Получается маленькие запросы могут быть в случае, например, двух потоков "тики", "изменение статуса заявки"?

G>http://msdn.microsoft.com/en-us/library/ms228969(v=VS.90).aspx

G>http://msdn.microsoft.com/en-us/library/ms228969(v=VS.100).aspx

Предлагаешь забить на RX и реализовывать асинхронные операции по MS рекомендациям?
Не понимаю, как конкретно это поможет проблемам с в случае использования потоков IObservable.

Проблема в том, что я не знаю RX и мучаю тебя вопросами или RX тут не очень подходит (для функциональности: "новый тик", "регистрация заявки", "снятие заявки")
Re[25]: Good practice по алгоритмизации в условиях асинхронн
От: gandjustas Россия http://blog.gandjustas.ru/
Дата: 25.08.10 12:19
Оценка:
Здравствуйте, sergunok, Вы писали:

S>Здравствуйте, gandjustas, Вы писали:


G>>Здравствуйте, sergunok, Вы писали:



S>>>>>Каким он может быть при условии, что снятие заявки до ее исполнения необходимо для некоторых стратегий?

G>>>>1)Что вернет RegisterOrder в случае отмены операции?
S>>>Canceled
G>>То есть и RegisterOrder вернет Canceled, CancelOrder вернет Canceled?
G>>Реализуешь такое?
S>Почему бы и нет.
S>И RegisterOrder и CancelOrder — асинхронные операции, причина завершения каждой из них не обязательно единственна.
Не стоит так делать.

G>>http://msdn.microsoft.com/en-us/library/ms228969(v=VS.90).aspx

G>>http://msdn.microsoft.com/en-us/library/ms228969(v=VS.100).aspx

S>Предлагаешь забить на RX и реализовывать асинхронные операции по MS рекомендациям?

Следовать рекомендациям надо, rx только упростит работу, никакой магии в нем нету. Он не сделает кривой интерфейс более правильным.


S>Проблема в том, что я не знаю RX и мучаю тебя вопросами или RX тут не очень подходит (для функциональности: "новый тик", "регистрация заявки", "снятие заявки")

Да все подходит, только не надо думать что Rx все проблемы решит. Без грамотного интефейса никакой Rx не поможет.
Re[4]: Good practice по алгоритмизации в условиях асинхронно
От: Кирилл Лебедев Россия http://askofen.blogspot.com/
Дата: 25.08.10 14:49
Оценка: 12 (2)
Здравствуйте, sergunok, Вы писали:

S>И куча if'ов в трех коллбеках, в привязке к состоянию.


На мой взгляд, причина Ваших проблем заключается в попытке создать сложную асинхронную систему на основе событийной модели. За основу для организации кода Вы выбираете события (заявка выполнена, заявка снята и др.), "навешиваете" на них обработчики и сталкиваетесь с проблемой, что код в каждом обработчике не является линейным, а, наоборот, содержит множество ветвлений, которые, к тому же, могут повторяться в разных обработчиках.

Вы это понимаете и сами пишите:

S>Таким образом в событие тика необходимо проверять какова цена текущей заявки, если она отличается от того, что нужно, инициировать снятие, при этом реально может придти событие как снятия так и исполнения (причем полного или неполного) и уже после успешного снятия выставить свою заявку..

S>Пhичем если процесс снятия уже инициирован, новый тик не должен бомбить биржу повторными снятиями.

S>У меня в голове сразу возникает понятие переменной с состояниями типа:

S>- заявка выставлена
S>- жду снятия

S>И куча if'ов в трех коллбеках, в привязке к состоянию.


Причина if'ов в коллбеках, как я уже писал выше, заключается в том, что Вы основываете код на событиях, т.е. за основу для группировки (классификации — называйте, как хотите) выбираете условия, а не действия, которые по этим условия выполняются.

Чтобы избежать излишних ветвлений, нужно, наоборот, строить код не на основе событий, а на основе элементарных операций, которые могут быть выполнены.

Например, в Вашем примере возможны такие операции:

  1. Получение и обновление информации о товаре.
  2. Получение и обновление информации о заявке.
  3. Подача заявки.
  4. Отмена заявки.
  5. Принятие решения о подаче или отмене заявки.
  6. И т.д.

Если дизайн всей программы построить вокруг этих операций, то код получится простым и не будет содержать глубоких ветвлений.

Как это сделать? Приведу один из возможных вариантов...

Для выполнения каждой операции заводим отдельный поток и даем ему осмысленное название. Получатся 5 потоков (они же — 5 сущностей):

  1. Обновляльщик цен
  2. Обновляльщик заявок
  3. Подаватель заявок
  4. Отменяльщик заявок
  5. Принимальщик решений

Опишем кратко назначение каждого из потоков.

Обновляльщик цен получает биржевой тик и меняет информацию о ценах в таблице товаров:

  1. Товар
  2. Предыдущая цена
  3. Идентификатор тика, когда была установлена предыдущая цена
  4. Текущая цена
  5. Идентификатор тика, когда была установлена текущая цена

Обновляльщик заявок получает события "заявка выполнена", "заявка отменена" и на основе этих событий модифицирует таблицу заявок:

  1. Идентификатор заявки
  2. Идентификатор товара
  3. Заявленная цена
  4. Статус

Подаватель заявок перебирает записи в таблице заявок и подает те заявки, которые нужно подать.

Отменяльщик заявок отменяет заявки.

Принимальщик решений просматривает таблицу товаров и таблицу заявок. В зависимости от изменения цен на товары модифицирует таблицу заявок (например, изменяет статус заявок для тех, которые нужно подать или которые нужно отменить).

Вот и всё!

Приведённое решение не является единственным. Например, некоторые потоки можно объединить. Например, можно объединить Подаватель заявок и Отменяльщик заявок.

Сами потоки тоже можно организовать по-разному. Например, они могут "просыпаться" по событию или же по времени. Например, Принимальщик решений может просматривать таблицу товаров раз в какое-то время, а Обновляльщик цен — ждать прихода очередного тика. Тут все зависит от конкретных условий задачи, например, от протокола обмена данными с сервером.
С уважением,
Кирилл Лебедев
Software Design blog — http://askofen.blogspot.ru/
Re[5]: Good practice по алгоритмизации в условиях асинхронно
От: sergunok  
Дата: 25.08.10 18:39
Оценка:
КЛ>Причина if'ов в коллбеках, как я уже писал выше, заключается в том, что Вы основываете код на событиях, т.е. за основу для группировки (классификации — называйте, как хотите) выбираете условия, а не действия, которые по этим условия выполняются.
КЛ>Чтобы избежать излишних ветвлений, нужно, наоборот, строить код не на основе событий, а на основе элементарных операций, которые могут быть выполнены.
Вы правы!!! Примерно в середине этого обсуждения пришел к тому, что:
1. привязываться нужно именно к действиям
2. грамотно распределять обработку этих действий по классам (старые добрые процедурные и ОО подходы)

При этом на текущий момент планирую оперировать не просто одним текущим действием, а очередью действий (первое из которых является текущим, а остальные должны быть выполнены после его завершения), поскольку в реальных стратегиях приходится некоторые действия ставить в очередь.

В примере из поста у меня получаются такие:
— Ожидание
— Реализация заявки
— Снятие заявки

Благодарю Вас за участие в обсуждении!
Re[5]: Good practice по алгоритмизации в условиях асинхронно
От: gandjustas Россия http://blog.gandjustas.ru/
Дата: 25.08.10 19:30
Оценка: 3 (1)
Здравствуйте, Кирилл Лебедев, Вы писали:

КЛ>Для выполнения каждой операции заводим отдельный поток и даем ему осмысленное название. Получатся 5 потоков (они же — 5 сущностей):

Это называется actor model.

Фактически один большой КА со сложным состоянием разбивается на несколько автоматов попроще, которые обмениваются сообщениями.

Недостаток — сложно отлаживать.

Есть другой вариант уменьшения сложности: отказаться от состояния и заниматься преобразованием потоков событий, особенно хорошо получается если часть преобразований можно выполнить "псевдосихронно", те код выглядит линейно, но в моменты ожидания поток не блокируется.
Таким образом значимое состояние будет передаваться в событиях, а то что не нужно (локальное состояние) будет спрятано внутри преобразований.

Такое сложнее писать (как зачастую сложнее писать функциональный код, по сравнению с императивным), но прекрасно отлаживатеся (логами на любом этапе преобразований) и тестируется (вместо потока событий подсовывается заранее заготовленный массив).

КЛ>Тут все зависит от конкретных условий задачи, например, от протокола обмена данными с сервером.

Кстати очень верное замечание, от него вообще говоря плясать надо.
Если протокол оперирует только двумя статусами заявки, типа Matched и Cancelled (хотя по факту еще еще переходные), то автомат, отвечающий за состояния, нужно держать на клиенте (это и будет actor).

А вот на потоки опираться не стоит, есть другие хорошие способы реализации actor model в .NET.
Re[6]: Good practice по алгоритмизации в условиях асинхронно
От: Кирилл Лебедев Россия http://askofen.blogspot.com/
Дата: 26.08.10 10:15
Оценка: 11 (2) +1
Здравствуйте, gandjustas, Вы писали:

КЛ>>Для выполнения каждой операции заводим отдельный поток и даем ему осмысленное название. Получатся 5 потоков (они же — 5 сущностей):

G>Это называется actor model.

Если "буржуйская" википедия не обманывает, то Actor Model — нечто совершенно другое, а не то, что я имел в виду. Русский термин Агентно-ориентированный подход — тоже из другой оперы.

Сразу скажу, отличие, на мой взгляд, принципиальное: Actor Model предполагает концентрацию на сущностях (акторах, агентах — называйте, как хотите), которые между собой взаимодействуют посредством обмена сообщениями, а я призываю прежде всего концентрироваться не на сущностях, а на элементарных операциях, которые нужно выполнить. Понятное дело, что далее операции будут делегированы отдельным сущностям и, при необходимости, могут быть распараллелены и выполняться в разных потоках. Но прежде всего — это операции и группировка их в последовательные или параллельные "цепочки".

Сам я такой подход называю "конвейерным", потому что он напоминает проектирование технологического процесса для массового или серийного производства, а готовый результат напоминает конвейер. Общее (и в программировании, и в производстве) — это избавление от ветвлений и обратных связей. Например, на производстве не допускается выполнение черновой операции над заготовкой после выполнения чистовой: сначала должны быть выполнены все черновые операции и только после этого — чистовые. Точно так же и в приведённом примере: нельзя подавать новую заявку на тот же товар до успешного снятия предыдущей заявки.

Логика событийной модели подсказывает, что в такой ситуации новую заявку нужно подавать в обработчике события Заявка Отменена. Но поскольку предыдущей заявки может и не быть, то код подачи заявки нужно будет продублировать и в обработчике события Биржевой Тик, а, возможно, и в обработчике события Заявка Удовлетворена Частично. Получается дублирование кода, а также — изобилие условных операторов, т.к. одни и те же условия приходится проверять в обработчиках разных событий. Если же концентрироваться на операциях (например, на операции Подача Новой Заявки), то такой проблемы не возникает.

Actor Model тоже не даёт приемлемого результата, т.к. не отвечает на вопрос Как находить акторов? В некотором роде, Actor Model напоминает OOD с его расплывчатыми рекомендациями по нахождению кандидатов в классы. Отличием является лишь то, что в C++ одни объекты вызывают методы других объектов, а в Actor Model одни акторы посылают сообщения другим акторам.

G>Фактически один большой КА со сложным состоянием разбивается на несколько автоматов попроще, которые обмениваются сообщениями.

G>Недостаток — сложно отлаживать.

Подозреваю, что сложность в отладке возникает именно из-за ошибок при выявлении акторов. Если грамотно расписать технологический процесс, как это принято у технологов на предприятиях, то код получается линейным и легко отлаживаемым. За последнее время я использовал конвейерную модель в 6-ти проектах (четырёх GPS-навигационных системах и двух консольных играх) и везде код получался достаточно простым.
С уважением,
Кирилл Лебедев
Software Design blog — http://askofen.blogspot.ru/
Re[6]: Good practice по алгоритмизации в условиях асинхронно
От: Кирилл Лебедев Россия http://askofen.blogspot.com/
Дата: 26.08.10 10:19
Оценка:
Здравствуйте, sergunok, Вы писали:

S>При этом на текущий момент планирую оперировать не просто одним текущим действием, а очередью действий (первое из которых является текущим, а остальные должны быть выполнены после его завершения), поскольку в реальных стратегиях приходится некоторые действия ставить в очередь.


Было бы здорово, если бы Вы привели пару примеров таких стратегий. Вне конкретной задачи очень сложно что-то рекомендовать...

S>В примере из поста у меня получаются такие:

S>- Ожидание
S>- Реализация заявки
S>- Снятие заявки

Тут опять же нужна конкретика. Пока непонятно, как будет выглядеть вся система в целом, если она будет основана на этих действиях.
С уважением,
Кирилл Лебедев
Software Design blog — http://askofen.blogspot.ru/
Re[7]: Good practice по алгоритмизации в условиях асинхронно
От: gandjustas Россия http://blog.gandjustas.ru/
Дата: 26.08.10 10:38
Оценка:
Здравствуйте, Кирилл Лебедев, Вы писали:

КЛ>Здравствуйте, gandjustas, Вы писали:


КЛ>>>Для выполнения каждой операции заводим отдельный поток и даем ему осмысленное название. Получатся 5 потоков (они же — 5 сущностей):

G>>Это называется actor model.

КЛ>Если "буржуйская" википедия не обманывает, то Actor Model — нечто совершенно другое, а не то, что я имел в виду. Русский термин Агентно-ориентированный подход — тоже из другой оперы.


КЛ>Сразу скажу, отличие, на мой взгляд, принципиальное: Actor Model предполагает концентрацию на сущностях (акторах, агентах — называйте, как хотите), которые между собой взаимодействуют посредством обмена сообщениями, а я призываю прежде всего концентрироваться не на сущностях, а на элементарных операциях, которые нужно выполнить. Понятное дело, что далее операции будут делегированы отдельным сущностям и, при необходимости, могут быть распараллелены и выполняться в разных потоках. Но прежде всего — это операции и группировка их в последовательные или параллельные "цепочки".


КЛ>Сам я такой подход называю "конвейерным", потому что он напоминает проектирование технологического процесса для массового или серийного производства, а готовый результат напоминает конвейер. Общее (и в программировании, и в производстве) — это избавление от ветвлений и обратных связей. Например, на производстве не допускается выполнение черновой операции над заготовкой после выполнения чистовой: сначала должны быть выполнены все черновые операции и только после этого — чистовые. Точно так же и в приведённом примере: нельзя подавать новую заявку на тот же товар до успешного снятия предыдущей заявки.


Видимо я начал комментировать реализацию, не поняв что имеется ввиду.

КЛ>Логика событийной модели подсказывает, что в такой ситуации новую заявку нужно подавать в обработчике события Заявка Отменена. Но поскольку предыдущей заявки может и не быть, то код подачи заявки нужно будет продублировать и в обработчике события Биржевой Тик, а, возможно, и в обработчике события Заявка Удовлетворена Частично. Получается дублирование кода, а также — изобилие условных операторов, т.к. одни и те же условия приходится проверять в обработчиках разных событий. Если же концентрироваться на операциях (например, на операции Подача Новой Заявки), то такой проблемы не возникает.


Тут непонятно. Как концентрация на операции Подача Новой Заявки поможет избежать дублирования кода, если эта операция может быть вызвана при наступлении разных событий?

КЛ>Actor Model тоже не даёт приемлемого результата, т.к. не отвечает на вопрос Как находить акторов? В некотором роде, Actor Model напоминает OOD с его расплывчатыми рекомендациями по нахождению кандидатов в классы. Отличием является лишь то, что в C++ одни объекты вызывают методы других объектов, а в Actor Model одни акторы посылают сообщения другим акторам.

Ну так и есть. Кроме того некоторые считают что actor model это труЪ ООП, как завещал Кей. Но в твоем посте акторы уже определены. Поэтому я и завел про них разговор.

G>>Фактически один большой КА со сложным состоянием разбивается на несколько автоматов попроще, которые обмениваются сообщениями.

G>>Недостаток — сложно отлаживать.

КЛ>Подозреваю, что сложность в отладке возникает именно из-за ошибок при выявлении акторов.

Возможно.

КЛ>Если грамотно расписать технологический процесс, как это принято у технологов на предприятиях, то код получается линейным и легко отлаживаемым. За последнее время я использовал конвейерную модель в 6-ти проектах (четырёх GPS-навигационных системах и двух консольных играх) и везде код получался достаточно простым.

Ну это как раз и будут преобразования потоков событий, про которые я говорил.
Re[8]: Good practice по алгоритмизации в условиях асинхронно
От: Кирилл Лебедев Россия http://askofen.blogspot.com/
Дата: 26.08.10 12:39
Оценка:
Здравствуйте, gandjustas, Вы писали:

G>Тут непонятно. Как концентрация на операции Подача Новой Заявки поможет избежать дублирования кода, если эта операция может быть вызвана при наступлении разных событий?


Концентрация на операции даёт нам критерий для классификации (или группировки). Не секрет, что общепринятым подходом является группировка по событиям или по условиям. В Event-based модели программист прежде всего выделяет команды (посылаемые серверу) и события (получаемые от сервера) и только затем задумывается о том, какими должны быть обработчики.

В нашем случае, топикастер приводит такие команды и события:

S>API, которое имеется:


S>
S>3 разновидности событий:
S>- биржевой тик Ticked(price) - тек. рыночная цена
S>- заявка выполнена(полностю или частично) OrderMatched(order) 
S>- заявка снята OrderCanceled(order)

S>2 типа воздействий
S>- выставить заявку RegisterOrder(order)
S>- снять заявку CancelOrder(order)
S>


При написании не асинхронного, а обычного кода в качестве основы для группировки нередко выступают не события, а условия. В результате код превращается в мешанину из операторов if:

if (A)
{
    if (B)
    {
        if (!C)
            do1();

        do2();
    }
    else
    {
        if (C)
            do1();
        else
            do3();

        do4();
    }
}
else
{
    if (B)
    {
        if (!C)
            do1();

        do5();
    }
    else
    {
        if (C)
            do1();
        else
            do6();

        do2();
    }
}


Если же за основу взять операцию, то нужно поступать противоположно: группировать не операции под условие, а условия под операцию. В нашем случае, для операции Подача Новой Заявки нужно просто перечислить все условия, при которых она будет выполняться, и объединить эти условия при помощи союзов "И" или "ИЛИ".

Очевидно, такими условиями будут:

1) Произошло изменение цены на товар
и
2) Новая Заявка не подана
и
3) Отсутствует текущая заявка на товар
или
3а) Текущая заявка на товар отменена
или
3б) Текущая заявка на товар удовлетворена не в полном объёме

Соответственно, основная часть программы будет представлять собой цикл:

bool bGo = true;

while (bGo)
{
    Отмена_Текущей_Заявки();
    Подача_Новой_Заявки();
    // ...
    Sleep(TimeToSleep);
}


Каждая из бизнес-операций в этом цикле будет проверять условия её выполнения и в случае их выполнения — совершать требуемые действия. Условия можно складировать в одной или нескольких таблицах (см. моё самое первое сообщение в этой теме), например, в Таблице Товаров и в Таблице Заявок.

Заполняться же эти таблицы могут в обработчиках событий: Биржевой Тик, Заявка Удовлетворена, Заявка Отменена. Но и об этих обработчиках лучше думать тоже не как об обработчиках, а как об элементарных операциях:

  1. Обновление информации о ценах
  2. Обновление статуса заявок

При таком подходе эти обработчики можно будет реализовать как через события, так и аналогично основному циклу программы:

void Обновление_Цен()
{
    bool bGo = true;

    while (bGo)
    {
        Получить_Биржевой_Тик();
        Обновить_Таблицу_Товаров();
        Sleep(TimeToSleep);
    }
}

void Обновление_Заявок()
{
    bool bGo = true;

    while (bGo)
    {
        Получить_все_события_ЗаявкаУдовлетворена();
        Получить_все_события_ЗаявкаОтменана();
        Обновить_Таблицу_Событий();
        Sleep(TimeToSleep);
    }
}


Соответственно, каждый из обработчиков запускается в отдельном потоке.
С уважением,
Кирилл Лебедев
Software Design blog — http://askofen.blogspot.ru/
Re[26]: Good practice по алгоритмизации в условиях асинхронн
От: sergunok  
Дата: 26.08.10 17:53
Оценка:
S>>Предлагаешь забить на RX и реализовывать асинхронные операции по MS рекомендациям?
G>Следовать рекомендациям надо, rx только упростит работу, никакой магии в нем нету. Он не сделает кривой интерфейс более правильным.

S>>Проблема в том, что я не знаю RX и мучаю тебя вопросами или RX тут не очень подходит (для функциональности: "новый тик", "регистрация заявки", "снятие заявки")

G>Да все подходит, только не надо думать что Rx все проблемы решит. Без грамотного интефейса никакой Rx не поможет.

Хмм.. Непонятно. Интерфейс библиотеки для доступа к данным биржи таков, какой есть. Честно говоря, не вижу в нем больших огрехов. Кроме этого, его можно много во что адаптировать.
Посмотрел про MS паттерны по асинхронной обработке — ничего принципиально нового в них не нашел. Эти паттерны даже больше похожи на гайдлайны. непонятно, чем они конкретно помогут.

А с применимости RX для подобных задач вопрос по-прежнему открыт. Под применением имею в виду не в коем случае не "лекарство от всех проблем", а скорее использование в качестве базового средства для обработки данных как по тикам, так и по заявкам и другим потокам, м.б. в какой-то степени для использования RX в качестве замены как раз способов организации асинхронности из MS паттернов (метод, стартующий операцию + event'ы, сигнализирующие о завершении).

То, в чем нет сомнения, так это то, что RX хорош для обработки "простого" потока однотипных событий. Поэкспериментировал, считать различные производные индикаторы на тиках — одно удовольствие.
Re[27]: Good practice по алгоритмизации в условиях асинхронн
От: gandjustas Россия http://blog.gandjustas.ru/
Дата: 26.08.10 23:14
Оценка:
Здравствуйте, sergunok, Вы писали:

S>>>Предлагаешь забить на RX и реализовывать асинхронные операции по MS рекомендациям?

G>>Следовать рекомендациям надо, rx только упростит работу, никакой магии в нем нету. Он не сделает кривой интерфейс более правильным.

S>>>Проблема в том, что я не знаю RX и мучаю тебя вопросами или RX тут не очень подходит (для функциональности: "новый тик", "регистрация заявки", "снятие заявки")

G>>Да все подходит, только не надо думать что Rx все проблемы решит. Без грамотного интефейса никакой Rx не поможет.

S>Хмм.. Непонятно. Интерфейс библиотеки для доступа к данным биржи таков, какой есть. Честно говоря, не вижу в нем больших огрехов.

А я вижу. Он не оперирует состоянием заявки, поэтому необходимо писать stateful код.

S>А с применимости RX для подобных задач вопрос по-прежнему открыт.

Подойди с другой стороны. Попробуй найти решение получше.

S>То, в чем нет сомнения, так это то, что RX хорош для обработки "простого" потока однотипных событий. Поэкспериментировал, считать различные производные индикаторы на тиках — одно удовольствие.

Ну а простые потоки можно преобразовывать и join_ить. Это примерно как работа с SQL, есть несколько простых операций: проекция, фильтрация, группировка, соединение, а запросы можно нарисовать какие угодно.

Я об этом и говорю что нужна сноровка чтобы сходу писать код с помощью Rx.
Re[9]: Good practice по алгоритмизации в условиях асинхронно
От: gandjustas Россия http://blog.gandjustas.ru/
Дата: 26.08.10 23:27
Оценка:
Здравствуйте, Кирилл Лебедев, Вы писали:

КЛ>Если же за основу взять операцию, то нужно поступать противоположно: группировать не операции под условие, а условия под операцию. В нашем случае, для операции Подача Новой Заявки нужно просто перечислить все условия, при которых она будет выполняться, и объединить эти условия при помощи союзов "И" или "ИЛИ".


Вот с этого места поподробнее, как объединить условия союзом "И". Условия получаются из событий.

КЛ>Очевидно, такими условиями будут:


КЛ>1) Произошло изменение цены на товар

КЛ>и
КЛ>2) Новая Заявка не подана
КЛ>и
КЛ>3) Отсутствует текущая заявка на товар
КЛ>или
КЛ>3а) Текущая заявка на товар отменена
КЛ>или
КЛ>3б) Текущая заявка на товар удовлетворена не в полном объёме

КЛ>Соответственно, основная часть программы будет представлять собой цикл:


КЛ>
КЛ>bool bGo = true;

КЛ>while (bGo)
КЛ>{
КЛ>    Отмена_Текущей_Заявки();
КЛ>    Подача_Новой_Заявки();
КЛ>    // ...
КЛ>    Sleep(TimeToSleep);
КЛ>}
КЛ>


КЛ>Каждая из бизнес-операций в этом цикле будет проверять условия её выполнения и в случае их выполнения — совершать требуемые действия. Условия можно складировать в одной или нескольких таблицах (см. моё самое первое сообщение в этой теме), например, в Таблице Товаров и в Таблице Заявок.


КЛ>Заполняться же эти таблицы могут в обработчиках событий: Биржевой Тик, Заявка Удовлетворена, Заявка Отменена. Но и об этих обработчиках лучше думать тоже не как об обработчиках, а как об элементарных операциях:


КЛ>

    КЛ>
  1. Обновление информации о ценах
    КЛ>
  2. Обновление статуса заявок
    КЛ>

КЛ>При таком подходе эти обработчики можно будет реализовать как через события, так и аналогично основному циклу программы:


КЛ>
КЛ>void Обновление_Цен()
КЛ>{
КЛ>    bool bGo = true;

КЛ>    while (bGo)
КЛ>    {
КЛ>        Получить_Биржевой_Тик();
КЛ>        Обновить_Таблицу_Товаров();
КЛ>        Sleep(TimeToSleep);
КЛ>    }
КЛ>}

КЛ>void Обновление_Заявок()
КЛ>{
КЛ>    bool bGo = true;

КЛ>    while (bGo)
КЛ>    {
КЛ>        Получить_все_события_ЗаявкаУдовлетворена();
КЛ>        Получить_все_события_ЗаявкаОтменана();
КЛ>        Обновить_Таблицу_Событий();
КЛ>        Sleep(TimeToSleep);
КЛ>    }
КЛ>}
КЛ>


КЛ>Соответственно, каждый из обработчиков запускается в отдельном потоке.


Вот и получились агенты (actor_ы), которые обмениваются информацией через разделяемое изменяемое состояние.
Это, как ни странно, самый худший вариант решения потому что:
1)Обращение к mutable state требует синхронизации
2)На каждый тип заявки будет создано по одному потоку, что при большом количестве заявок плохо скажется на производительности
3)Любой вариант ожидания по времени под высокой нагрузкой не работает из-за низкой точности системного таймера
4)pull модель оповещения из внешней системы чревата тем что при определенной нагрузке очередь не будет успевать разгребаться и могут начаться необъяснимые глюки, которые сложно отловить при дебаге.

Вообще твой вариант хорошо работает при низкой, желательно фиксированной нагрузке и ни разу не масштабируется.
Твое решение хорошее с точки зрения анализа, но ужасное с точки зрения реализации. А топик начался именно с реализации.
Re[10]: Good practice по алгоритмизации в условиях асинхронн
От: Кирилл Лебедев Россия http://askofen.blogspot.com/
Дата: 27.08.10 09:14
Оценка:
Здравствуйте, gandjustas, Вы писали:

G>Вот с этого места поподробнее, как объединить условия союзом "И". Условия получаются из событий.

Непонятен вопрос. Вы хотите узнать:

1) Как из событий получить условия?
2) Или же — как упростить условный оператор, если условий будет слишком много?
3) Или же что-то иное?

G>Вот и получились агенты (actor_ы), которые обмениваются информацией через разделяемое изменяемое состояние.

Да, но хочу отметить, что агенты — это результат проектирования. Моё возражение было против названия метода (Actor Model или агентно-ориентированное проектирование), т.к. такое название даёт неправильные ориентиры проектировщику, и он (вольно или невольно) пытается отыскать агенты, вместо того, чтобы выявлять операции.

G>Это, как ни странно, самый худший вариант решения потому что:

G>1)Обращение к mutable state требует синхронизации
Вы слегка поторопились с таким выводом. Мы еще не дошли до конкретной реализации, которая будет воплощена в коде. Можно сделать так, чтобы к таблицам (товаров, заявок и др.) действительно обращались все потоки, а можно сделать и по-другому: к таблицам будет обращаться только основной поток, в котором выполняется бизнес-логика, а задания для остальных потоков рассылаются через очереди сообщений.

G>2)На каждый тип заявки будет создано по одному потоку, что при большом количестве заявок плохо скажется на производительности

Зачем? Мы же договорились, что группировку будем выполнять по операциям, а не по условиям или объектам (в Вашем случае — заявкам).

G>3)Любой вариант ожидания по времени под высокой нагрузкой не работает из-за низкой точности системного таймера

Вы думаете события ОС срабатывают как-то иначе? Шедулер тоже раз в определенное время проверяет флаг срабатывания события и, если флаг установлен, то "будит" ожидающий поток.

G>4)pull модель оповещения из внешней системы чревата тем что при определенной нагрузке очередь не будет успевать разгребаться и могут начаться необъяснимые глюки, которые сложно отловить при дебаге.

Под pull моделью Вы очевидно понимаете то, что "получальщики" новых цен или заявок сами инициируют запросы к серверу? Тут я с Вами вынужден не согласиться, т.к. если Ваши обработчики, которые вызываются по event'ам, тоже не справляются с нагрузкой, то возникает та же самая проблема. Если входной поток событий (тиков, event'ов) чрезвычайно насыщен, Вам нужно задуматься о масштабировании системы в любом случае, независимо от того, какую модель (pull или push) Вы используете.
С уважением,
Кирилл Лебедев
Software Design blog — http://askofen.blogspot.ru/
Re[11]: Good practice по алгоритмизации в условиях асинхронн
От: gandjustas Россия http://blog.gandjustas.ru/
Дата: 27.08.10 10:59
Оценка:
Здравствуйте, Кирилл Лебедев, Вы писали:

КЛ>Здравствуйте, gandjustas, Вы писали:


G>>Вот с этого места поподробнее, как объединить условия союзом "И". Условия получаются из событий.

КЛ>Непонятен вопрос. Вы хотите узнать:

КЛ>1) Как из событий получить условия?

КЛ>2) Или же — как упростить условный оператор, если условий будет слишком много?
КЛ>3) Или же что-то иное?
Больше интересует первое.

G>>Это, как ни странно, самый худший вариант решения потому что:

G>>1)Обращение к mutable state требует синхронизации
КЛ>Вы слегка поторопились с таким выводом. Мы еще не дошли до конкретной реализации, которая будет воплощена в коде. Можно сделать так, чтобы к таблицам (товаров, заявок и др.) действительно обращались все потоки, а можно сделать и по-другому: к таблицам будет обращаться только основной поток, в котором выполняется бизнес-логика, а задания для остальных потоков рассылаются через очереди сообщений.
На PDC08 был отличный доклад о быстродействии и масштабируемости систем. Там очень хорошо описано "откуда ноги растут". http://channel9.msdn.com/pdc2008/TL38/
Вкратце суть: есть две модели общения push — когда клиенту приходит событие, и pull — когда клиент сам запрашивает данные.
Когда преобразуется push в pull в каком-либо звене передачи, то появляются такие message loop_ы, а когда наоборот — буферы.

Схематично записывается так: -> — pull; <- — push. На стыках получаются <--> message loop, -><- — буфер (чаще всего очередь).

Читается слева направо, справа сервер, слева клиент. Схема двойственна, там где для сервера push, там для клиента pull и наоборот.
Чтобы добиться максимальной производительности надо чтобы во всей цепи передачи данных стрелки были направлены в одну сторону.


Если взять обычный веб-сервер, то для него схема выглядит так: (клиент) -> (сервер) <- (worker thread) -> (приложение на сервере). worker thread нужен потому что код приложения выполняется синхронно и нельзя допустить чтобы сервер "курил" пока обрабатыватеся один запрос.
А вот Node.js попытался сделать такую схему (клиент) -> (сервер) -> (приложение на сервере на js) за счет интерпретируемости js его можно выполнять асинхронно.

Теперь смотрим: торговый сервер push_ит клиентам тики, агент складывает эти данные в буфер, клиенты оттуда pull_ят данные. Далее предлагается сделать одного агента, который pull_ит состояния и push_ит задания другим агентам.
Итого схема выглядит так (исполнитель команд) -> (очередь команд) <- (основной поток с бизнес-логикой) -> (обработчик событий) <- (сервер).

С помощью Rx можно сделать так: (исполнитель команд) <- (обработчик событий) <- (сервер). А если эта схема не будет оперировать изменяемым состоянием, то отлаживать её станет элементарно. На событиях тоже можно так сделать, но тогда сильно перемешается код обработчика и исполнителя, будет куча if_ов и некоторое состояние (такое обычно называют конечным автоматом).

Но есть маленькая проблема, сервер не передает достаточно данных чтобы мог работать обработчик событий, а все необходимые данные появляются после выполнения команды. Поэтому три варианта:
1) смешать все вместе (вариант с событиями)
2) сделать выходной поток событий исполнителя еще одним входным потоком обработчика (как я приводил с помощью Rx). Получается неочевидный код.
3) Изолировать состояние в stateful агенте, который принимает одни события и плюется другими. Ухудшается отладка.
Именно этот вопрос на самом деле беспокоит автора (и Rx тут совершенно не при чем)

Реализовывать все на stateful агентах — далеко не самое лучшее решение.


G>>2)На каждый тип заявки будет создано по одному потоку, что при большом количестве заявок плохо скажется на производительности

КЛ>Зачем? Мы же договорились, что группировку будем выполнять по операциям, а не по условиям или объектам (в Вашем случае — заявкам).
А потому что циклы Отменить-Отправить-ДождатьсяЗавершения несинхронно для разных заявок работают.

G>>3)Любой вариант ожидания по времени под высокой нагрузкой не работает из-за низкой точности системного таймера

КЛ>Вы думаете события ОС срабатывают как-то иначе? Шедулер тоже раз в определенное время проверяет флаг срабатывания события и, если флаг установлен, то "будит" ожидающий поток.
А я не говорил про событие ОС. Асинхронные операции (начиная с чтения из сетевого потока) немного по-другому делаются. И вообще на потоки лучше не завязываться.
Re[12]: Good practice по алгоритмизации в условиях асинхронн
От: Кирилл Лебедев Россия http://askofen.blogspot.com/
Дата: 27.08.10 22:37
Оценка:
Здравствуйте, gandjustas, Вы писали:

КЛ>>1) Как из событий получить условия?

КЛ>>2) Или же — как упростить условный оператор, если условий будет слишком много?
КЛ>>3) Или же что-то иное?
G>Больше интересует первое.

Путем сохранения событий в некотором хранилище. Например, информацию о заявках и о событиях, произошедших с ними, можно хранить в таблице заявок. Об этом я уже писал здесь
Автор: Кирилл Лебедев
Дата: 25.08.10
.

G>Чтобы добиться максимальной производительности надо чтобы во всей цепи передачи данных стрелки были направлены в одну сторону.

В целом, я согласен. Но если рассматривать конкретные примеры, то возникают вопросы (см. ниже).

G>А вот Node.js попытался сделать такую схему (клиент) -> (сервер) -> (приложение на сервере на js) за счет интерпретируемости js его можно выполнять асинхронно.

На мой взгляд, схема тут не однонаправленная, а двунаправленная. Не смотря на то, что Клиент инициирует запрос к Серверу, после запроса он все равно входит в режим ожидания, т.к. Клиенту нужен ответ. А ожидание — это и цикл, и Sleep. Если ответ считывается синхронно, то цикл будет выполняться в исходном потоке, если асинхронно — то в другом. Но суть от этого не меняется. Связь двунаправленная.

Аналогичная ситуация получается и со второй частью схемы: Сервер -> Приложение на JS. Чтобы отослать данные Клиенту, Серверу все равно их надо получить от Приложения. А это означает, что ему нужно не только посылать запросы к Приложению, но и каким-то образом получать от него ответы. Опять-таки получается цикл (например, что очередь событий не пуста) и ожидание (если делать нечего).

G>Теперь смотрим: торговый сервер push_ит клиентам тики, агент складывает эти данные в буфер, клиенты оттуда pull_ят данные. Далее предлагается сделать одного агента, который pull_ит состояния и push_ит задания другим агентам.

G>Итого схема выглядит так (исполнитель команд) -> (очередь команд) <- (основной поток с бизнес-логикой) -> (обработчик событий) <- (сервер).
Мне кажется, схема все-таки будет немного другая:

(1) Сервер -> Обновляльщик Цен -> (Таблица Товаров) -> Принимальщик Решений -> Подавальщик/Отменяльщик Заявок -> Сервер
(2) Сервер -> Обновляльщик Заявок -> (Таблица Заявок) -> Принимальщик Решений


Принимальщик Решений работает как с таблицей товаров (цен), так и с таблицей заявок. У него два источника данных. Поскольку данные (цены и информация о статусах заявок) поступают с разной частотой, то Принимальщик Решений крутится в своём потоке и работает со своей заданной частотой, независимо от частоты обновления цен и частоты поступления событий о статусах заявок.

В принципе, при необходимости Принимальщик Решений можно объединить с Обновляльщиком Цен и синхронизировать его работу с тиками от Сервера:

(1) Сервер -> Обновляльщик Цен / Принимальщик Решений -> Подавальщик/Отменяльщик Заявок -> Сервер
(2) Сервер -> Обновляльщик Заявок -> (Таблица Заявок) -> Обновляльщик Цен / Принимальщик Решений


Если Обновляльщик Цен, Обновляльщик Заявок, Принимальщик Решений, Подавальщик/Отменяльщик Заявок выполняются на одном компе (т.е. для обмена данными между ними нет сетевого взаимодействия), то не вижу вообще никаких проблем с производительностью, т.к. уже писал, что системный шедулер использует тот же самый системный таймер, и любая асинхронная модель — это тот же самый отдельный поток со своим циклом ожидания.

G>С помощью Rx можно сделать так: (исполнитель команд) <- (обработчик событий) <- (сервер). А если эта схема не будет оперировать изменяемым состоянием, то отлаживать её станет элементарно. На событиях тоже можно так сделать, но тогда сильно перемешается код обработчика и исполнителя, будет куча if_ов и некоторое состояние (такое обычно называют конечным автоматом).


Насколько я понял топикастера (ну и суть задачи из его описания), проблема будет существовать, не зависимо от используемой технологии, т.к. у Принимальщика Решений (Вы его называете исполнитель команд) не один, а два (или даже три) источника данных:

  1. Биржевой Тик
  2. События: заявка удовлетворена и заявка отклонена

И чтобы не дублировать код (и не посылать одни и те же заявки на сервер) нужно:

(1) либо запустить Принимальщик Решений в отдельном потоке со своей частотой, которая не зависит от частоты поступления событий;
(2) либо синхронизировать работу Принимальщика Решений с частотой поступления какого-либо события (я предложил синхронизировать с биржевым тиком)

И, мне кажется, что по-другому все равно никак не сделать, не зависимо от используемой технологии. Даже если технология будет сама создавать необходимые потоки, сама отвечать за синхронизацию доступа к данным и прятать все это от программиста.

G>Но есть маленькая проблема, сервер не передает достаточно данных чтобы мог работать обработчик событий, а все необходимые данные появляются после выполнения команды. Поэтому три варианта:

G>1) смешать все вместе (вариант с событиями)
На мой взгляд, не вариант, т.к. в этом случае действительно ухудшается отладка и сопровождение. Плюс будет дублирование кода.

G>2) сделать выходной поток событий исполнителя еще одним входным потоком обработчика (как я приводил с помощью Rx). Получается неочевидный код.

Не понимаю, зачем. (Если можете объяснить без Rx, то будет хорошо, т.к. я на .NET не программирую).

G>3) Изолировать состояние в stateful агенте, который принимает одни события и плюется другими. Ухудшается отладка.

В принципе, да. Но не сильно. Если грамотно сделать декомпозицию, то Принимальщик Решений, Обновляльщик Цен и Обновляльщик Заявок можно вообще отлаживать независимо.

G>>>2)На каждый тип заявки будет создано по одному потоку, что при большом количестве заявок плохо скажется на производительности

КЛ>>Зачем? Мы же договорились, что группировку будем выполнять по операциям, а не по условиям или объектам (в Вашем случае — заявкам).
G>А потому что циклы Отменить-Отправить-ДождатьсяЗавершения несинхронно для разных заявок работают.
Как мне представляется, это и не важно. Если Принимальщик Решений будет работать со своей частотой (или будет согласован с частотой биржевого тика).

G>А я не говорил про событие ОС. Асинхронные операции (начиная с чтения из сетевого потока) немного по-другому делаются. И вообще на потоки лучше не завязываться.

Вне зависимости от апи, реализуются-то они всё равно через thread'ы, mutex'ы и event'ы. Хотя согласен, что термин thread при описании концептуального решения (т.е. решения, не привязанного к определенной технологии) не очень хорош. Но мне пока не удается подобрать какой-то другой более адекватный термин.
С уважением,
Кирилл Лебедев
Software Design blog — http://askofen.blogspot.ru/
Re[13]: Good practice по алгоритмизации в условиях асинхронн
От: gandjustas Россия http://blog.gandjustas.ru/
Дата: 28.08.10 00:21
Оценка:
Здравствуйте, Кирилл Лебедев, Вы писали:

КЛ>Здравствуйте, gandjustas, Вы писали:


КЛ>>>1) Как из событий получить условия?

КЛ>>>2) Или же — как упростить условный оператор, если условий будет слишком много?
КЛ>>>3) Или же что-то иное?
G>>Больше интересует первое.

КЛ>Путем сохранения событий в некотором хранилище. Например, информацию о заявках и о событиях, произошедших с ними, можно хранить в таблице заявок. Об этом я уже писал здесь
Автор: Кирилл Лебедев
Дата: 25.08.10
.


G>>Чтобы добиться максимальной производительности надо чтобы во всей цепи передачи данных стрелки были направлены в одну сторону.

КЛ>В целом, я согласен. Но если рассматривать конкретные примеры, то возникают вопросы (см. ниже).

G>>А вот Node.js попытался сделать такую схему (клиент) -> (сервер) -> (приложение на сервере на js) за счет интерпретируемости js его можно выполнять асинхронно.

КЛ>На мой взгляд, схема тут не однонаправленная, а двунаправленная. Не смотря на то, что Клиент инициирует запрос к Серверу, после запроса он все равно входит в режим ожидания, т.к. Клиенту нужен ответ.
Это да

КЛ>А ожидание — это и цикл, и Sleep.

А это — нет. В этом и суть хорошей асинхронной обработки что для ожидания ничего не нужно. Фактически клиент отправив запрос(ы) на сервер рождает поток событий-ответов. Ответы также push_атся в клиента и он их обрабатывает. Причем каждый ответ — асинхронный (!) поток байт.


КЛ>Аналогичная ситуация получается и со второй частью схемы: Сервер -> Приложение на JS. Чтобы отослать данные Клиенту, Серверу все равно их надо получить от Приложения. А это означает, что ему нужно не только посылать запросы к Приложению, но и каким-то образом получать от него ответы. Опять-таки получается цикл (например, что очередь событий не пуста) и ожидание (если делать нечего).

И снова нет. Приложение на JS "слушает" поток входящих запросов и отдает поток ответов.

Если приложению делать нечего, то оно проваливается в ожидание ОС. Которое обычно реализуется командой hlt, отключающей процессор до прерывания.

G>>Теперь смотрим: торговый сервер push_ит клиентам тики, агент складывает эти данные в буфер, клиенты оттуда pull_ят данные. Далее предлагается сделать одного агента, который pull_ит состояния и push_ит задания другим агентам.

G>>Итого схема выглядит так (исполнитель команд) -> (очередь команд) <- (основной поток с бизнес-логикой) -> (обработчик событий) <- (сервер).
КЛ>Мне кажется, схема все-таки будет немного другая:

КЛ>
КЛ>(1) Сервер -> Обновляльщик Цен -> (Таблица Товаров) -> Принимальщик Решений -> Подавальщик/Отменяльщик Заявок -> Сервер
КЛ>(2) Сервер -> Обновляльщик Заявок -> (Таблица Заявок) -> Принимальщик Решений
КЛ>

Ну тогда я хочу посмотреть на реализацию. Без дополнительных библиотек такое сделать сложно.
Если (Таблица Товаров) и (Таблица Заявок) не сами передают данные Принимателю решений, то стрелки будет в другую сторону.
Кроме того нужно учитывать что Принимальщик Решений будет опираться на сочетание состояний из таблиц, поэтому как будет организована push модель в таком случае даже представит не могу.

КЛ>Принимальщик Решений работает как с таблицей товаров (цен), так и с таблицей заявок. У него два источника данных. Поскольку данные (цены и информация о статусах заявок) поступают с разной частотой, то Принимальщик Решений крутится в своём потоке и работает со своей заданной частотой, независимо от частоты обновления цен и частоты поступления событий о статусах заявок.

Таки pull понадобится чтобы не зависеть от частоты источников.

КЛ>В принципе, при необходимости Принимальщик Решений можно объединить с Обновляльщиком Цен и синхронизировать его работу с тиками от Сервера:


КЛ>
КЛ>(1) Сервер -> Обновляльщик Цен / Принимальщик Решений -> Подавальщик/Отменяльщик Заявок -> Сервер
КЛ>(2) Сервер -> Обновляльщик Заявок -> (Таблица Заявок) -> Обновляльщик Цен / Принимальщик Решений
КЛ>


КЛ>Если Обновляльщик Цен, Обновляльщик Заявок, Принимальщик Решений, Подавальщик/Отменяльщик Заявок выполняются на одном компе (т.е. для обмена данными между ними нет сетевого взаимодействия), то не вижу вообще никаких проблем с производительностью, т.к. уже писал, что системный шедулер использует тот же самый системный таймер, и любая асинхронная модель — это тот же самый отдельный поток со своим циклом ожидания.


G>>С помощью Rx можно сделать так: (исполнитель команд) <- (обработчик событий) <- (сервер). А если эта схема не будет оперировать изменяемым состоянием, то отлаживать её станет элементарно. На событиях тоже можно так сделать, но тогда сильно перемешается код обработчика и исполнителя, будет куча if_ов и некоторое состояние (такое обычно называют конечным автоматом).


КЛ>Насколько я понял топикастера (ну и суть задачи из его описания), проблема будет существовать, не зависимо от используемой технологии,

Проблема то будет, а её решение будет сильно зависеть от выбранных средств и дизайна.

КЛ>у Принимальщика Решений (Вы его называете исполнитель команд) не один, а два (или даже три) источника данных:


КЛ>

    КЛ>
  1. Биржевой Тик
    КЛ>
  2. События: заявка удовлетворена и заявка отклонена
    КЛ>
Именно 3, потому что должно учитываться текущее состояние заявки, которое генерируется Подавальщиком/Отменяльщиком Заявок.

КЛ>И чтобы не дублировать код (и не посылать одни и те же заявки на сервер) нужно:


КЛ>(1) либо запустить Принимальщик Решений в отдельном потоке со своей частотой, которая не зависит от частоты поступления событий;

Плохо, потому что частоты входящих событий не фиксированы.

КЛ>(2) либо синхронизировать работу Принимальщика Решений с частотой поступления какого-либо события (я предложил синхронизировать с биржевым тиком)

А я бы предложил заниматься преобразованием потоков, чтобы получить на входе Принимальщика Решений именно те данные, которые нужны и именно тогда когда они появятся.
Это называется join_ить потоки событий.

КЛ>И, мне кажется, что по-другому все равно никак не сделать, не зависимо от используемой технологии. Даже если технология будет сама создавать необходимые потоки, сама отвечать за синхронизацию доступа к данным и прятать все это от программиста.

Ну вообще-то нет, потому что используемые средства (языки\библиотеки) влияют на дизайн решений.

G>>Но есть маленькая проблема, сервер не передает достаточно данных чтобы мог работать обработчик событий, а все необходимые данные появляются после выполнения команды. Поэтому три варианта:

G>>1) смешать все вместе (вариант с событиями)
КЛ>На мой взгляд, не вариант, т.к. в этом случае действительно ухудшается отладка и сопровождение. Плюс будет дублирование кода.
Дублирования можно банально избежать с помощью отдельных методов, а дублирования условий — созданием явных состояний (но их может стать очень много).

G>>2) сделать выходной поток событий исполнителя еще одним входным потоком обработчика (как я приводил с помощью Rx). Получается неочевидный код.

КЛ>Не понимаю, зачем. (Если можете объяснить без Rx, то будет хорошо, т.к. я на .NET не программирую).
Без Rx не смогу, потому что именно он вводит понятие потока событий (асинхронной коллекции). Вкратце если для обычной коллекции (списка) у нас есть операция получения итератора и прохода по коллекции в одном направлении с помощью этого итератора (последовательного получения всех элементов), то для асинхронной коллекции получает дуальная схема — мы подписываемся на события с помощью нашего наблюдателя, который что-либо делает при получении следующего элемента.

Для обычной коллекции можно написать комбинаторы, которые возвращают новую коллекцию (коллекции ленивые) итератор которой будет выполнять некоторые преобразования над итератором исходного списка (например пропускать элементы, применять для них функции итд). Аналогичное можно сделать и с асинхронным коллекциями и поиметь ту же саму семантику (theorems for free).

G>>3) Изолировать состояние в stateful агенте, который принимает одни события и плюется другими. Ухудшается отладка.

КЛ>В принципе, да. Но не сильно. Если грамотно сделать декомпозицию, то Принимальщик Решений, Обновляльщик Цен и Обновляльщик Заявок можно вообще отлаживать независимо.

G>>>>2)На каждый тип заявки будет создано по одному потоку, что при большом количестве заявок плохо скажется на производительности

КЛ>>>Зачем? Мы же договорились, что группировку будем выполнять по операциям, а не по условиям или объектам (в Вашем случае — заявкам).
G>>А потому что циклы Отменить-Отправить-ДождатьсяЗавершения несинхронно для разных заявок работают.
КЛ>Как мне представляется, это и не важно. Если Принимальщик Решений будет работать со своей частотой (или будет согласован с частотой биржевого тика).
Ну тогда будет много буферов и лупов, которые плохо скажутся на производительности. Кроме того фиксированная частота работает в среднем плохо, потому что частоты входящих потоков не фиксированы и варьируются в очень широком диапазоне.

G>>А я не говорил про событие ОС. Асинхронные операции (начиная с чтения из сетевого потока) немного по-другому делаются. И вообще на потоки лучше не завязываться.

КЛ>Вне зависимости от апи, реализуются-то они всё равно через thread'ы, mutex'ы и event'ы. Хотя согласен, что термин thread при описании концептуального решения (т.е. решения, не привязанного к определенной технологии) не очень хорош. Но мне пока не удается подобрать какой-то другой более адекватный термин.
Если под словом thread имеется ввиду системный thread, то это неверно. Если поток исполнения, который может быть как system thread, fiber, так и сопрограммой (coroutine) или (в более общем случае) цепочкой продолжений (continuation), то тут обсуждать нечего. Поток исполнения всегда присутствует в том или ином виде. Хотя например при задании dataflow явного потока нет.
Re: Good practice по алгоритмизации в условиях асинхронности
От: asimonenko  
Дата: 28.08.10 06:53
Оценка:
Все равно все сведется к автомату, ИМХО. Т.е. данные появляются асинхронно, и при определенных условиях (наборе данных) ты должен выполнить определенное действие.

То же самое с сериализацией — при десериализации ты логично "двигаешь" автомат в нужную точку последовательностью входящих данных.

Словом, КА — просто и надежно. Только его нужно сразу выявить, формализовать (куда-нибудь записать диаграммку) и реализовать на ранних стадиях, пока не наворочена неформализованная куча -)
Re[2]: Good practice по алгоритмизации в условиях асинхронно
От: gandjustas Россия http://blog.gandjustas.ru/
Дата: 28.08.10 17:04
Оценка:
Здравствуйте, asimonenko, Вы писали:

A>Все равно все сведется к автомату, ИМХО. Т.е. данные появляются асинхронно, и при определенных условиях (наборе данных) ты должен выполнить определенное действие.


A>То же самое с сериализацией — при десериализации ты логично "двигаешь" автомат в нужную точку последовательностью входящих данных.


A>Словом, КА — просто и надежно. Только его нужно сразу выявить, формализовать (куда-нибудь записать диаграммку) и реализовать на ранних стадиях, пока не наворочена неформализованная куча -)


Для автоматов нет способов борьбы со сложностью. Если состояний станет слишком много, то не не будет возможности как-то исправить.
Re[14]: Good practice по алгоритмизации в условиях асинхронн
От: Кирилл Лебедев Россия http://askofen.blogspot.com/
Дата: 29.08.10 11:45
Оценка:
Здравствуйте, gandjustas, Вы писали:

КЛ>>А ожидание — это и цикл, и Sleep.

G>А это — нет. В этом и суть хорошей асинхронной обработки что для ожидания ничего не нужно. Фактически клиент отправив запрос(ы) на сервер рождает поток событий-ответов. Ответы также push_атся в клиента и он их обрабатывает. Причем каждый ответ — асинхронный (!) поток байт.

Даже если "забыть" про то, что данные по сети передаются не потоками, а пакетами, а иллюзию потока создаёт протокол транспортного уровня (например, TCP), который получает пакеты протокола сетевого уровня, проверяет их целостность, сортирует в порядке отправления, отправляет запросы на испорченные или не дошедшие пакеты, и всё это он делает не сам по себе, а, естественно, в системном потоке, которым, как и остальными системными потоками, управляет системный шедулер (это к вопросу о точности системного таймера и потерях времени на переключении потоков).

Даже если мы "забываем" про всю эту "кухню" и считаем, что у нас просто есть "поток байт", он же сам по себе не возьмётся из ниоткуда. Асинхронная процедура чтения этого потока байт как раз и заключается в том, что существует некий поток, скрытый от программиста, который периодически проверяет, пришли ли новые байты, и если они не пришли, то "засыпает" на некоторое время, а если пришли — отсылает их получателю.

КЛ>>Аналогичная ситуация получается и со второй частью схемы: Сервер -> Приложение на JS. Чтобы отослать данные Клиенту, Серверу все равно их надо получить от Приложения. А это означает, что ему нужно не только посылать запросы к Приложению, но и каким-то образом получать от него ответы. Опять-таки получается цикл (например, что очередь событий не пуста) и ожидание (если делать нечего).

G>И снова нет. Приложение на JS "слушает" поток входящих запросов и отдает поток ответов.

Простейший вопрос: после отсылки запроса приложению JS, сервер пытается прочесть ответы. Что происходит, если ответы ещё не дошли?

На мой взгляд, возможны 3 варианта:

1) Сервер впадает в цикл ожидания до тех пор, пока не придет ответ.
2) Сервер использует асинхронную процедуру чтения, т.е. создает вспомогательный поток, который и ждет ответа, а сам занимается своими делами.
3) Сервер занимается своими делами и периодически пытается прочитать ответ: если ответ пришел, то обрабатывает его; если не пришел, то продолжает заниматься другими делами (та же асинхронность, но вместо вытесняющей используется кооперативная многозадачность).

G>Если приложению делать нечего, то оно проваливается в ожидание ОС. Которое обычно реализуется командой hlt, отключающей процессор до прерывания.

Оно же не отключает процессор сервера?

G>Ну тогда я хочу посмотреть на реализацию. Без дополнительных библиотек такое сделать сложно.

Вы не могли бы конкретизировать, что Вам непонятно?

G>Если (Таблица Товаров) и (Таблица Заявок) не сами передают данные Принимателю решений, то стрелки будет в другую сторону.

Даже если обмен данными между потоками происходит через очередь сообщений, Принимальщик Решений все равно должен извлечь сообщение из очереди для того, чтобы его обработать.

КЛ>>у Принимальщика Решений (Вы его называете исполнитель команд) не один, а два (или даже три) источника данных:

КЛ>>

    КЛ>>
  1. Биржевой Тик
    КЛ>>
  2. События: заявка удовлетворена и заявка отклонена
    КЛ>>
G>Именно 3, потому что должно учитываться текущее состояние заявки, которое генерируется Подавальщиком/Отменяльщиком Заявок.
Я писал о внешних источниках — не о внутренних.

КЛ>>(1) либо запустить Принимальщик Решений в отдельном потоке со своей частотой, которая не зависит от частоты поступления событий;

G>Плохо, потому что частоты входящих событий не фиксированы.
Чем плохо?

КЛ>>(2) либо синхронизировать работу Принимальщика Решений с частотой поступления какого-либо события (я предложил синхронизировать с биржевым тиком)

G>А я бы предложил заниматься преобразованием потоков, чтобы получить на входе Принимальщика Решений именно те данные, которые нужны и именно тогда когда они появятся.
G>Это называется join_ить потоки событий.
Как это практически будет реализовываться, если частоты поступления событий разные? Да и некоторые события могут вообще не поступить?

G>Без Rx не смогу, потому что именно он вводит понятие потока событий (асинхронной коллекции). Вкратце если для обычной коллекции (списка) у нас есть операция получения итератора и прохода по коллекции в одном направлении с помощью этого итератора (последовательного получения всех элементов), то для асинхронной коллекции получает дуальная схема — мы подписываемся на события с помощью нашего наблюдателя, который что-либо делает при получении следующего элемента.

Понимаю, что это может сократить код при реализации, но не понимаю, как это влияет на проектирование и на выработку концептуального решения?

G>Ну тогда будет много буферов и лупов, которые плохо скажутся на производительности. Кроме того фиксированная частота работает в среднем плохо, потому что частоты входящих потоков не фиксированы и варьируются в очень широком диапазоне.

Не думаю, что обычное приложение с тремя-пятью потоками, выполняемое под Windows на процессоре с 2-4 ядрами как-то резко скажется на производительности. Соответственно, буферов и лупов тоже будет немного — по числу потоков.
С уважением,
Кирилл Лебедев
Software Design blog — http://askofen.blogspot.ru/
Re[15]: Good practice по алгоритмизации в условиях асинхронн
От: gandjustas Россия http://blog.gandjustas.ru/
Дата: 29.08.10 12:07
Оценка:
Здравствуйте, Кирилл Лебедев, Вы писали:

КЛ>Здравствуйте, gandjustas, Вы писали:


КЛ>>>А ожидание — это и цикл, и Sleep.

G>>А это — нет. В этом и суть хорошей асинхронной обработки что для ожидания ничего не нужно. Фактически клиент отправив запрос(ы) на сервер рождает поток событий-ответов. Ответы также push_атся в клиента и он их обрабатывает. Причем каждый ответ — асинхронный (!) поток байт.

КЛ>Даже если "забыть" про то, что данные по сети передаются не потоками, а пакетами, а иллюзию потока создаёт протокол транспортного уровня (например, TCP), который получает пакеты протокола сетевого уровня, проверяет их целостность, сортирует в порядке отправления, отправляет запросы на испорченные или не дошедшие пакеты, и всё это он делает не сам по себе, а, естественно, в системном потоке, которым, как и остальными системными потоками, управляет системный шедулер (это к вопросу о точности системного таймера и потерях времени на переключении потоков).


КЛ>Даже если мы "забываем" про всю эту "кухню" и считаем, что у нас просто есть "поток байт", он же сам по себе не возьмётся из ниоткуда. Асинхронная процедура чтения этого потока байт как раз и заключается в том, что существует некий поток, скрытый от программиста, который периодически проверяет, пришли ли новые байты, и если они не пришли, то "засыпает" на некоторое время, а если пришли — отсылает их получателю.

Ну правильно, поток блокируется на время выполнения пересылки получателю. А остальное время он может заниматься другими делами, в том числе отправлять пакеты другим получателям. Почти всегда хватает одного системного потока на процессор.

КЛ>>>Аналогичная ситуация получается и со второй частью схемы: Сервер -> Приложение на JS. Чтобы отослать данные Клиенту, Серверу все равно их надо получить от Приложения. А это означает, что ему нужно не только посылать запросы к Приложению, но и каким-то образом получать от него ответы. Опять-таки получается цикл (например, что очередь событий не пуста) и ожидание (если делать нечего).

G>>И снова нет. Приложение на JS "слушает" поток входящих запросов и отдает поток ответов.

КЛ>Простейший вопрос: после отсылки запроса приложению JS, сервер пытается прочесть ответы. Что происходит, если ответы ещё не дошли?

А сервер не пытается прочесть, js приложение push_ает ответ серверу, а тот в сою очередь push_ает ответ клиенту.
КЛ>На мой взгляд, возможны 3 варианта:

КЛ>1) Сервер впадает в цикл ожидания до тех пор, пока не придет ответ.

КЛ>2) Сервер использует асинхронную процедуру чтения, т.е. создает вспомогательный поток, который и ждет ответа, а сам занимается своими делами.
КЛ>3) Сервер занимается своими делами и периодически пытается прочитать ответ: если ответ пришел, то обрабатывает его; если не пришел, то продолжает заниматься другими делами (та же асинхронность, но вместо вытесняющей используется кооперативная многозадачность).
Второй вариант, толь он по сути не создает потоков, а использует небольшой пул.

G>>Если приложению делать нечего, то оно проваливается в ожидание ОС. Которое обычно реализуется командой hlt, отключающей процессор до прерывания.

КЛ>Оно же не отключает процессор сервера?
Отключает.

G>>Ну тогда я хочу посмотреть на реализацию. Без дополнительных библиотек такое сделать сложно.

КЛ>Вы не могли бы конкретизировать, что Вам непонятно?
Обновляльщик Цен -> (Таблица Товаров) -> Принимальщик Решений

Вот эта цепочка.

G>>Если (Таблица Товаров) и (Таблица Заявок) не сами передают данные Принимателю решений, то стрелки будет в другую сторону.

КЛ>Даже если обмен данными между потоками происходит через очередь сообщений, Принимальщик Решений все равно должен извлечь сообщение из очереди для того, чтобы его обработать.

КЛ>>>у Принимальщика Решений (Вы его называете исполнитель команд) не один, а два (или даже три) источника данных:

КЛ>>>

    КЛ>>>
  1. Биржевой Тик
    КЛ>>>
  2. События: заявка удовлетворена и заявка отклонена
    КЛ>>>
G>>Именно 3, потому что должно учитываться текущее состояние заявки, которое генерируется Подавальщиком/Отменяльщиком Заявок.
КЛ>Я писал о внешних источниках — не о внутренних.
А чем они хуже\лучше внутренних?

КЛ>>>(1) либо запустить Принимальщик Решений в отдельном потоке со своей частотой, которая не зависит от частоты поступления событий;

G>>Плохо, потому что частоты входящих событий не фиксированы.
КЛ>Чем плохо?
Тем что в среднем неэффективно получится, или реакция будет запаздывать, или ресурсы прожигаться будут. Так или иначе масштабируемость ухудшится.

КЛ>>>(2) либо синхронизировать работу Принимальщика Решений с частотой поступления какого-либо события (я предложил синхронизировать с биржевым тиком)

G>>А я бы предложил заниматься преобразованием потоков, чтобы получить на входе Принимальщика Решений именно те данные, которые нужны и именно тогда когда они появятся.
G>>Это называется join_ить потоки событий.
КЛ>Как это практически будет реализовываться, если частоты поступления событий разные?

Вот так
Observable.Join(
  //Если новая минимальная цена и ожидаем события, то покупаем
  minPrices.And(idleState).Then((p, _) => Buy(p)),
  //Если новая минимальная цена и покупаем, то отменяем
  minPrices.And(buyState)
           .Then((p, s) =>
                 {
                   this.trade.CancelOrder(s.Order);
                   return new State(null, StateEn.CancellingBuy, p);
                 }),
  //Если статус заявки изменился на matched  и покупаем, то переходим в ожидание
  orderMatched.And(buyState).Then((o, s) => new State(null, StateEn.Idle, s.Price)),
  //Если статус заявки изменился и отменяем, то покупаем по новой цене 
  orderChanged.And(cancellingState).Then((_, s) => Buy(s.Price))
)


КЛ>Да и некоторые события могут вообще не поступить?

Тогда и заджоиненное не наступит.


G>>Без Rx не смогу, потому что именно он вводит понятие потока событий (асинхронной коллекции). Вкратце если для обычной коллекции (списка) у нас есть операция получения итератора и прохода по коллекции в одном направлении с помощью этого итератора (последовательного получения всех элементов), то для асинхронной коллекции получает дуальная схема — мы подписываемся на события с помощью нашего наблюдателя, который что-либо делает при получении следующего элемента.

КЛ>Понимаю, что это может сократить код при реализации, но не понимаю, как это влияет на проектирование и на выработку концептуального решения?
Потому что вводится новая абстракция (первоклассная сущность) — поток событий.
Примерно также при ФП дизайн получается не такой же как при ООП.

G>>Ну тогда будет много буферов и лупов, которые плохо скажутся на производительности. Кроме того фиксированная частота работает в среднем плохо, потому что частоты входящих потоков не фиксированы и варьируются в очень широком диапазоне.

КЛ>Не думаю, что обычное приложение с тремя-пятью потоками, выполняемое под Windows на процессоре с 2-4 ядрами как-то резко скажется на производительности. Соответственно, буферов и лупов тоже будет немного — по числу потоков.
Ну мы ведь не об обычных приложениях говорим. Когда начинают возится с асинхронным IO, то рассчитывают на большую нагрузку. И тС привел пример приложений, в которых время реакции очень важно.
Подождите ...
Wait...
Пока на собственное сообщение не было ответов, его можно удалить.