вторник, 19 января 2016 г.

[prog.c++11.sobjectizer] И еще несколько слов о примере intercom_statechart

Пример с имитацией работы домофона оказался очень удобным для проверки реализации иерархических конечных автоматов в SObjectizer-5.5.15. Код этого примера теперь входит в дистрибутив SObjectizer-а.

Пример с домофоном оказался интересным не только с точки зрения ИКА. Он так же показывает пару других особенностей SObjectizer, которые по-отдельности могут вызывать вопросы у людей, привыкших к реализации Actor Model в Erlang/Akka или C++ Actor Framework. И которые, наверное, раздувают объем кода агентов в тривиальных примерах, вроде простейшего ping-pong-а. Но которые, на мой взгляд, оказываются чрезвычайно полезными в более-менее сложном и приближенном к реальной жизни коде.

Вот об этих вещах и хочется поговорить отдельно.

Пример intercom_statechart хорош тем, что в нем задействуется несколько агентов, каждый из которых отвечает за свой аспект работы имитатора домофона. Есть основной агент-контроллер, который делает львиную долю работы. Есть вспомогательные агенты вроде inactivity_watcher-а (контролирует время отсутствия активности со стороны пользователя), display (имитирует дисплей домофона), ringer (имитирует работу устройства связи с квартирами) и т.д.

Все эти агенты выполняют собственный набор действий. В классических ИКА активность каждого из них можно было бы представить в виде параллельного (конкурентного/ортогонального) подсостояния общего конечного автомата для домофона. В SO-5.5.15 поддержки параллельных подсостояний нет, т.к. не очевидно, что с этим делать на практике. Но в SObjectizer можно создавать агентов для различных активностей и объединять этих агентов в группы.

Понятие кооперация в SObjectizer -- это и есть та самая группа агентов, каждый из которых не имеет смысла без других агентов группы. Отсюда и такие вещи, как транзакционная регистрация и дерегистрация коопераций, т.е. либо все агенты успешно добавляются в SObjectizer Run-Time, либо ни один из них. В коде это выражается приблизительно вот так:

so_5::mbox_t create_intercom( so_5::environment_t & env )
{
   so_5::mbox_t intercom_mbox;
   env.introduce_coop( [&]( so_5::coop_t & coop ) {
      intercom_mbox = env.create_mbox();

      coop.make_agent< controller >( intercom_mbox );
      coop.make_agent< inactivity_watcher >( intercom_mbox );
      coop.make_agent< keyboard_lights >( intercom_mbox );
      coop.make_agent< display >( intercom_mbox );
      coop.make_agent< ringer >( intercom_mbox );
   } );

   return intercom_mbox;
}

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

Без кооперации пришлось бы выстраивать последовательный запуск агентов. Сначала бы стартовал один. Потом он бы запустил остальных...

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

В SObjectizer все это происходит автоматически в рамках кооперации. Т.е. размещение группы агентов в одной кооперации -- это как создание группы процессов в Erlang-е под контролем супервизора "one-for-all".

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

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

Вот, например, в случае с имитатором домофона есть сообщения, которые имитируют нажатия на кнопки на консоли домофона: key_cancel, key_bell, key_grid, key_digit. Но какому из агентов эти сообщения следует отсылать? Ведь в них заинтересованы два агента: агент-контроллер и агент, который следит за временем бездействия пользователя. Или взять сообщение deactivate, которое должно перевести домофон в режим ожидания и в котором заинтересовано сразу четыре агента...

Если бы в SObjectizer сообщения можно было отсылать только в почтовый ящик только одного агента, то пришлось бы отсылать сообщения от клавиатуры какому-то одному из них. Скажем, агенту-контроллеру. А уже этот агент, кроме своей основной работы, еще бы и пересылал клавиатурные сообщения агенту inactivity_watcher-у. Что плохо, т.к. у агента-контроллера и так логика не сказать, чтобы тривиальная:

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

Но зачем делать этот агент-прокси вручную? Если можно разрешить почтовому ящику иметь более одного подписчика!

Вот как раз поэтому в SObjectizer существуют Multi-Producer/Multi-Consumer почтовые ящики. Которые можно считать аналогами механизма Publish-Subscribe. Т.е. MPMC-ящик -- это аналог MQ, а типы сообщений в нем -- это аналоги конкретных топиков. Агенты из одной кооперации совместно используют MPMC-ящик, подписываясь на те сообщения, которые им нужны. Так, в примере intercom_statechart на сообщение, скажем, key_digit, подписываются сразу два агента. А на сообщение deactivate -- сразу четыре.

Причем в реальных задачах, в которых SObjectizer использовался, необходимость в MPMC-ящиках возникала настолько часто, что первоначально в SO-5 был только один тип почтового ящика -- MPMC. А классический для модели акторов, Multi-Producer/Single-Consumer ящик, появился только через несколько лет после начала использования SO-5 в продакшене.


Как уже неоднократно говорилось, SObjectizer никогда не был исследовательским проектом. Он изначально создавался для упрощения коммерческой разработки софта. И всегда в таком качестве использовался. Думаю, поэтому SObjectizer не является ни реализацией чистой модели акторов, ни реализацией чистой модели Publish-Subscribe. Это некий симбиоз, основные качества которого проявляются на проектах объемом от 10K строк и выше.

Так что пример intercom_statechart удачен еще и тем, что он позволил продемонстрировать кооперации и MPMC-ящики на относительно небольшой и относительно простой задачке.

Ну и не могу не сказать, что работая над этим примером получил такое же удовольствие, как и несколько раньше от примера machine_control.

Комментариев нет: