SObjectizer: I Love This Game!

Автор: Евгений Охотников
Интервэйл

Источник: RSDN Magazine #4-2005
Опубликовано: 31.03.2006
Версия текста: 1.0
Введение
О чем речь
Немного истории
Коллизии с терминологией
Синхронность vs. асинхронность
Коротко о SObjectizer
Основные понятия
Состав SObjectizer
Hello, world!
Тактико-технические параметры
Что же в нем такого?
Событийно-ориентированное программирование
Многопоточность
Распределенность
Надежность?
Ложка дегтя
Технические проблемы
Социально-политические проблемы
Организационные проблемы
Terra Incognita
Ниша SObjectizer
Найдется ли место для синхронности?
Заключение
Благодарности

Введение

О чем речь

В этой статье я попытаюсь немного расказать об инструменте, в разработке которого мне повезло участвовать. Об инструменте, который я использую каждый день. Об инструменте, который изменил способ моего мышления так же, как когда-то объектно-ориентированное программирование. Это — SObjectizer, внутренний продукт компании Интервэйл (http://www.intervale.ru), который используется в качестве основного инструмента при создании C++-проектов.

В настоящее время разработчики SObjectizer стоят перед вопросом: стоит ли из внутреннего инструмента сделать SObjectizer одним из продуктов компании? Вопрос далеко не праздный и не однозначный. Главное препятствие состоит в специализации SObjectizer — инструмента для разработки ПО. С моей точки зрения у закрытых, проприетарных инструментов очень мало шансов на успех. Слишком уж большая конкуренция со стороны OpenSource-движения. Поэтому SObjectizer, если он станет самостоятельным продуктом, обязательно будет распространяться по одной из OpenSource-лицензий (сейчас выбор делается между BSD- и GPL-лицензиями).

Тем не менее, выпуск SObjectizer как отдельного продукта станет серьезным испытанием. Поэтому перед принятием окончательного решения хочется прояснить два основных вопроса:

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

Немного истории

Началось все с того, что в гомельском Конструкторском Бюро Системного Программирования (КБСП) в 1994-96 годах сложилась команда, которая под руководством Аркадия Косарева попыталась создать объектно-ориентированную SCADA-систему (Supervisory Control and Data Acquisition). Шли мы к получению результата долго и трудно. Но дошли и получили работающую версию продукта, получившего название SCADA Objectizer (см. http://msk.nestor.minsk.by/kg/2000/37/kg03710.html). Именно в SCADA Objectizer были заложены основные идеи и принципы — агенты, состояния, сообщения, события, диспетчер (как раз все то, что мы назвали агентно-ориентированным подходом). Но главное, что тогда произошло изменение способа мышления лично у меня — я стал испытывать затруднения с решением задач, в которых нельзя было применить агентный подход.

В 2000 году этот этап в истории SObjectizer печальным образом закончился: по экономическим причинам коллектив разработчиков SCADA Objectizer распался, и дальнейшее развитие SCADA Objectizer, по тем же причинам, оказалось невозможным.

Второе рождение произошло в 2002-м году в компании Интервэйл, когда несколько бывших сотрудников КБ решили, что агентный подход имеет смысл возродить. Тогда и появился SObjectizer — полностью переделанная реализация идей SCADA Objectizer. С тех пор SObjectizer используется нами при создании всех C++-проектов. И лично у меня нет сомнений в правильности выбора в пользу SObjectizer. Но об этом речь пойдет ниже.

Коллизии с терминологией

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

"an entity whose state is viewed as consisting of mental components such as beliefs, capabilities, choices, and commitments" (см., например, Agent-Oriented Programming : A Practical Evaluation, http://www.cs.berkeley.edu/~davidp/cs263).

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

Синхронность vs. асинхронность

Краеугольным камнем SObjectizer является его асинхронная природа — все взаимодействия внутри SObjectizer осуществляются путем отсылки асинхронных сообщений. Хорошо это или плохо, сказать однозначно нельзя. Как обычно, все зависит от конкретной задачи. Где-то асинхронность дает значительные преимущества, где-то же, наоборот, приводит к излишним трудозатратам и необоснованному росту сложности.

Мне очень не хочется в статье скатываться на религиозную войну "синхронность vs асинхронность". На эту тему и так уже сказано достаточно (см., например, Jim Waldo, Geoff Wyant, Ann Wollrath, and Sam Kendall, A Note on Distributed Computing [http://research.sun.com/techrep/1994/smli_tr-94-29.pdf] или Andrew S. Tanenbaum, Robbert van Renesse, A Critique of the Remote Procedure Call Paradigm [http://www.cs.vu.nl/pub/papers/amoeba/euteco88.ps.Z]). Тем не менее, в той или иной форме такие противопоставления по ходу статьи будут проявляться. Но это будет следствием того, что сейчас SObjectizer асинхронен, а не фанатичной убежденности в том, что кроме асинхронности ничего больше не заслуживает внимания.

Коротко о SObjectizer

Здесь я попытаюсь дать максимально упрощенное описание того, что же такое агентно-ориентированный подход в SObjectizer, и что же такое сам SObjectizer. Аналогичное описание в руководстве по программированию в SObjectizer (SObjectizer-4 Book, http://eao197.narod.ru/desc/so_4_book.pdf) занимает около двадцати страниц. Поэтому за более полной информацией об SObjectizer лучше обращаться к этому руководству.

Основные понятия

Основными понятиями в агентно-ориентированном подходе являются: состояние, сообщение, событие и агент. Они достаточно тесно связаны между собой, и их определения также зависят друг от друга.

Состояние

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

Понятие состояния существует только на уровне агентно-ориентированного подхода, в C++-программе не требуется определять каких-либо классов, методов или переменных — за работу с состоянием агента отвечает SObjectizer.

Сообщение

Сообщение — единственный механизм взаимодействия между агентами в SObjectizer. Имя сообщения должно быть уникальным в рамках агента. На C++ сообщение должно быть представлено структурой или классом.

Для отсылки сообщения SObjectizer предоставляет несколько функций send_msg. Сообщения могут выполнять как роль сигналов (сообщение без данных, важен сам факт отсылки сообщения), так и роль обычных сообщений (содержащих данные).

Для получения сообщения агент должен подписаться на него. Сообщения в SObjectizer идентифицируются по паре имен: имени агента, который владеет сообщением, и имени самого сообщения. Такой способ идентификации позволяет различать сообщения от агентов одного типа (владеющих одинаковыми наборами сообщений). Понятие “владения агента сообщением” возникает из-за необходимости как-то структурировать множество всех сообщений. Например, если в противопожарной системе есть множество однотипных датчиков, каждый из которых отсылает сообщение о своем состоянии, то нужно как-то различать, к какому именно датчику относится конкретное сообщение. В SObjectizer эта проблема была решена привязкой имен сообщений к именам агентов — если агент зарегистрирован в SObjectizer, есть и его сообщения. Как только агент дерегистрируется, то вместе с ним исчезают и сообщения, которыми он владел.

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

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

Событие

Событие — это реакция агента на сообщение. Каждое событие должно иметь уникальное в рамках агента имя. На C++ событие реализуется нестатическим методом C++-класса агента.

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

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

Агент

Агент — это объект, который обладает состояниями, событиями и сообщениями. В SObjectizer существует понятие класса агентов как совокупности однотипных агентов с одинаковыми наборами состояний, событий и сообщений. В C++-программе класс агентов представляется в виде C++-класса, производного от специального базового класса agent_t. Агенты представляются в виде экземпляров данного C++-класса.

Состав SObjectizer

SObjectizer Run-Time

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

Диспетчер

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

В состав SObjectizer входят несколько готовых диспетчеров:

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

Принцип работы

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

  1. Кто-то вызывает send_msg для отсылки сообщения. Функции send_msg указывается имя сообщения и, если необходимо, имя получателя.
  2. SObjectizer проверяет существование сообщения и, если такое сообщение действительно существует, создает экземпляр сообщения. В случае отложенного сообщения экземпляр сообщения отдается специальному объекту-таймеру для отсчета тайм-аута. В противном случае начинается диспетчеризация сообщения.
  3. Для всех событий, которые были подписаны на сообщение, формируется список заявок диспетчеру. Если сообщение отсылалось адресно, то в этот список включаются только события агента-получателя.
  4. Заявки передаются диспетчеру для дальнейшей диспетчеризации.
  5. Диспетчер распределяет эти заявки по очередям заявок своих рабочих нитей. Например, диспетчер с одной рабочей нитью помещает все заявки в одну очередь. Диспетчер с активными объектами помещает заявки в очередь той нити, которая обслуживает конкретного агента.
  6. Рабочая нить диспетчера извлекает из своей очереди очередную заявку. Она проверяет, может ли событие быть обработано в текущем состоянии агента. Если может, то в контексте этой рабочей нити у объекта-агента вызывается метод-обработчик события. В противном случае событие игнорируется. После обработки очередной заявки сама заявка уничтожается.
  7. После уничтожения всех заявок, которые были порождены экземпляром сообщения, SObjectizer автоматически уничтожает экземпляр сообщения (если только сообщение не является периодическим).

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

В штатных диспетчерах SObjectizer очереди заявок содержат заявки от разных экземпляров сообщений, упорядоченные сначала по убыванию приоритета, а затем по возрастанию времени создания заявки. Например, если сначала были порождены заявки a1(2), a2(1), a3(0) (в скобках указан приоритет соответствующих событий), а затем b1(3), b2(1), b3(1), b4(0), то очередь заявок будет иметь вид: b1(3), a1(2), a2(1), b2(1), b3(1), a3(0), b4(0).

Еще одной важной особенностью диспетчеризации в SObjectizer является то, что проверка возможности обработки события производится не в момент выставления заявок диспетчеру, а в момент извлечения заявки из очереди рабочей нити перед ее непосредственным исполнением. Это делает возможным, например, такой случай: на один инцидент агент подписывает три события: e1(2), e2(1), e3(0). Все они имеют разные приоритеты и обрабатываются в разных состояниях. При возникновении инцидента этих событий заявки на выполнение e1, e2, e3 выставляются диспетчеру. Когда рабочая нить диспетчера извлекает заявку e1(2), то оказывается, что агент может обработать ее в своем текущем состоянии. Нить диспетчера вызывает обработчик события e1, и этот обработчик переводит агента в другое состояние, в котором возможна обработка e2. Далее нить диспетчера берет заявку e2(1), и оказывается, что новое текущее состояние агента позволяет обработать это событие. Обработчик e2 переводит агент в третье состояние, в котором разрешается событие e3. В результате нить диспетчера вызовет и обработчик e3. Но, если бы допустимость обработки события проверялась в момент выставления заявок диспетчеру, то заявки e2 и e3 были бы потеряны.

Hello, world!

Пришло время показать, как выглядит C++-код с использованием SObjectizer. Пусть это будет общепринятый пример "Hello, world". Однако нужно сделать несколько предварительных замечаний. Во-первых, данный пример не может показать преимуществ SObjectizer перед традиционным программированием на C++, т.к. эти преимущества начинают проявляться в гораздо более сложных и объемных задачах. Во-вторых, этот пример иллюстрирует, что для описания класса агента и для регистрации агента в SObjectizer необходимо выполнить достаточно много дополнительных действий, выполнение которых не автоматизировано и чревато досадными ошибками. Но об этом речь еще зайдет.

Пока же предлагаю взглянуть на код примера, в котором запускается SObjectizer Run-Time, стартует один агент, который в своем единственном обработчике события выводит "Hello, world", после чего завершает работу SObjectizer Run-Time и тем самым всего примера:

        #include <iostream>

// Загружаем основные заголовочные файлы SObjectizer.#include <so_4/rt/h/rt.hpp>
#include <so_4/api/h/api.hpp>

// Загружаем описание нити таймера и диспетчера.#include <so_4/timer_thread/simple/h/pub.hpp>
#include <so_4/disp/one_thread/h/pub.hpp>

// C++-описание класса агента.class  a_hello_t
  : public so_4::rt::agent_t
{
  // Псевдоним для базового типа.typedef so_4::rt::agent_t base_type_t;
  public :
    a_hello_t()
    :
      // Сразу задаем имя агента.
      base_type_t("a_hello")
    {}
    virtual ~a_hello_t()
    {}

    virtualconstchar *
    so_query_type() const;

    virtualvoid
    so_on_subscription()
    {
      // Нужно подписать наше единственное событие.
      so_subscribe("evt_start",
        so_4::rt::sobjectizer_agent_name(),
        "msg_start");
    }

    // Обработка начала работы агента в системе.void
    evt_start()
    {
      std::cout << "Hello, world!" << std::endl;

      // Завершаем работу примера.
      so_4::api::send_msg(
        so_4::rt::sobjectizer_agent_name(),
        "msg_normal_shutdown", 0);
    }
};

// Описание класса агента для SObjectizer-а.
SOL4_CLASS_START(a_hello_t)

  // Одно событие.
  SOL4_EVENT(evt_start)

  // И одно состояние.
  SOL4_STATE_START(st_normal)
    // С одним событием.
    SOL4_STATE_EVENT(evt_start)
  SOL4_STATE_FINISH()

SOL4_CLASS_FINISH()

int
main()
{
  // Наш агент.
  a_hello_t a_hello;
  // И кооперация для него.
  so_4::rt::agent_coop_t  a_hello_coop(a_hello);

  // Запускаем SObjectizer Run-Time.
  so_4::ret_code_t rc = so_4::api::start(
    so_4::disp::one_thread::create_disp(
      so_4::timer_thread::simple::create_timer_thread(),
      so_4::auto_destroy_timer),
    so_4::auto_destroy_disp,
    &a_hello_coop);
  if(rc) {
    // Запустить SObjectizer Run-Time не удалось.
    std::cerr << "start: " << rc << std::endl;
  }

  returnint(rc);
}

Тактико-технические параметры

Последняя версия SObjectizer — это почти 110 тысяч строк С++-кода. Работает на плаформах: Windows, различных Unix-ах (Linux, FreeBSD, Solaris), HP NonStop Kernel. Т.е. на всем, что было в наличии.

Оценить производительность систем на SObjectizer достаточно сложно. Естественно, что система диспетчеризации SObjectizer привносит свои накладные расходы. Но в реальных проектах наибольшее время занимают прикладные действия. Синтетический же тест производительности показывает, что в случае десяти активных агентов SObjectizer обеспечивает отсылку и диспетчеризацию пятидесяти тысяч сообщений в секунду (Intel Mobile Pentium 1.5GHz, Windows XP SP2, 512Mb).

Что же в нем такого?

После такого поверхностного знакомства с SObjectizer я попробую описать основные моменты, из-за которых мне нравится использовать SObjectizer.

Событийно-ориентированное программирование

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

В ООП принято считать, что объекты взаимодействуют друг с другом посредством отсылки сообщений. Поэтому так легко при проектировании рисовать объекты в виде прямоугольничков и соединять их линиями, обозначающими отсылку сообщения. Но при реализации спроектированной модели оказывается, что в C++ единственная форма взаимодействия объектов — это синхронный вызов метода. Поэтому исходные квадратики и стрелочки превращаются в довольно запутанный программный код.

Например, пусть в пожарной сигнализации есть датчик дыма, который в случае задымленности должен отсылать сообщение объекту-сигнализации. Как это можно сделать на традиционном C++? Очевидно, что вариантов может быть много. Скажем, у объекта-сигнализации есть очередь, в которую датчики помещают свои сообщения, а объект-сигнализация висит в цикле ожидания появления сообщений в очереди:

        class Alarm_Handler
{
public :
  void push_message(std::auto_ptr< Alarm_Message > msg)
  {
    Scoped_Lock lockup(lock_);
    queue_.push(msg);

    has_elements_.notify();
  }

  void start()
  {
    while(true)
    {
      Scoped_Lock lockup(lock_);
      if(queue_.empty())
        has_elements_.wait();

          // Обработка события.
    }
  }
  ...
};

class Smoke_Sensor
{
public :
  void start()
  {
    while(true)
    {
      acquire_data();
      if(smoke_level_too_high())
        alarm_.push_message(create_alarm_message());
        ...
    }
  }
};

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

        class Alarm_Handler : public so_4::rt::agent_t
{
public :
  void evt_alarm_message(const Alarm_Message & cmd)
  {
    // Обработка события.
  }
...
};

class Smoke_Sensor
{
public :
  void start()
  {
    while(true)
      {
        acquire_data();
        if(smoke_level_too_high())
          so_4::api::send_msg_safely(alarm_agent_name(), "msg_alarm",
              create_alarm_message());
      }
  }
...
};

С таким подходом, на мой взгляд, проще выразить в коде исходный замысел проектировщика. Здесь также имеются дополнительные преимущества.

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

Если объект Alarm_Handler является агентом, то сам SObjectizer определяет, в какие моменты времени Alarm_Handler будет работать. Alarm_Handler автоматически завершит свою работу одновременно с SObjectizer Run-Time. Если же Alarm_Handler работает в собственном цикле извлечения сообщений из собственной очереди, то даже выполнение такого простого действия, как завершение работы Alarm_Handler-а, потребует дополнительных усилий как при разработке Alarm_Handler-а, так и при его использовании.

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

А что, если на сигнал какого-то датчика должно среагировать несколько Alarm_Handler-ов? Причем, сам датчик даже не знает про это — он знает только, что ему нужно поместить сообщение в очередь объекта Alarm_Handler-а. В SObjectizer этому ничто не препятствует. В самом простом случае агент-датчик широковещательно отсылает свое сообщение, на которое подписывается нужное количество заинтересованных Alarm_Handler-ов. Если же датчик нельзя представить в виде агента, то можно подписать Alarm_Handler-ы на то сообщение, которое отсылает датчик. Т.е. получится, что датчик знает только об одном Alarm_Handler-е. Этот Alarm_Handler подписывается на собственное сообщение, и остальные, заинтересованные в данном сообщении, Alarm_Handler-ы также подписываются на это же самое сообщение:

        class Alarm_Handler : public so_4::rt::agent_t
  {
  public :
    void so_on_subscription()
      {
        // Подписываемся на собственное сообщение.
        so_subscribe("evt_alarm_message", "msg_alarm");
        ...
      }
    ...
  };

// Отвечает за включение пожарной сирены.class Fire_Siren_Switcher : public so_4::rt::agent_t
  {
  public :
    void so_on_subscription()
      {
        // Подписываемся на сообщение Alarm_Handler-а.
        so_subscribe("evt_turn_on", alarm_handler_name(), "msg_alarm");
        ...
      }
    ...
  };

// Отвечает за остановку лифтов при возникновении пожарной опасности.class  Elevator_Stopper : public so_4::rt::agent_t
  {
  public :
    void so_on_subscription()
      {
        // Подписываемся на сообщение Alarm_Handler-а.
        so_subscribe("evt_fire_threat", alarm_handler_name(), "msg_alarm");
        ...
      }
    ...
  };

Многопоточность

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

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

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

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

Если вернуться к примеру с агентом Alarm_Handler, можно увидеть, что ему не нужны объекты синхронизации. В mutex-ах нет необходимости потому, что штатные диспетчеры SObjectizer гарантируют, что все события Alarm_Handler-а будут запущены в контексте одного и того же потока. Из-за этого атрибуты объекта Alarm_Handler не нуждаются в синхронизации доступа, а также нет нужды использовать условную переменную (condition variable), т.к. у Alarm_Handler-а нет собственного цикла выборки сообщений. Вместо этого сам SObjectizer определяет, когда должно быть обработано то или иное событие Alarm_Handler-а, и именно в этот момент "будит" Alarm_Handler.

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

Достигается такая гибкость за счет того, что привязка агента к диспетчеру осуществляется посредством назначения агенту специального свойства (traits). И сделать это может не только сам агент, но и любой клиент данного агента. Например, во время инициализации приложения можно написать что-то вроде:

        // Пожарная сигнализация для первого этажа.
Alarm_Handler * a_floor_1 = new Alarm_Handler(...);
// Этот агент должен быть членом активной группы.
so_4::disp::active_group::make_member(*a_floor_1, "alarm_handlers");

// Пожарная сигнализация для второго этажа.
Alarm_Handler * a_floor_2 = new Alarm_Handler(...);
// Входит в ту же группу.
so_4::disp::active_group::make_member(*a_floor_2, "alarm_handlers");

// Агент для включения пожарной сирены вообще// не должен иметь отдельного контекста.
Fire_Siren_Switcher * a_switcher = new Fire_Siren_Switcher(...);

// А агент для обесточивания лифтов должен быть активным агентом.
Elevator_Stopper * a_elevator_stopper = new Elevator_Stopper(...);
so_4::disp::active_obj::make_active(*a_elevator_stopper);

А если со временем выяснится, что, скажем, агент a_floor_2 должен обрабатывать сигналы от слишком большого количества датчиков, и ему нужно выделить отдельную нить, то достаточно будет изменить всего одну строку:

        // Пожарная сигнализация для второго этажа.
Alarm_Handler * a_floor_2 = new Alarm_Handler(...);
// Теперь должна быть отдельным активным объектом.
so_4::disp::active_obj::make_active(*a_floor_2);

При этом, естественно, SObjectizer не запрещает привязывать агентов к конкретным механизмам диспетчеризации, если это необходимо. Например, для работы с датчиком дыма необходимо в бесконечном цикле выполнять опрос порта ввода-вывода. Можно сделать это в виде отдельного потока, а можно в виде активного агента (который сам себя объявляет активным):

        class Smoke_Sensor : public so_4::rt::agent_t
  {
    typedef so_4::rt::agent_t base_type_t;
  public :
    Smoke_Sensor(const std::string & agent_name)
      :  base_type_t(agent_name)
      {
        so_4::disp::active_obj::make_active(*this);
      }
    ...
    // Событие, которое генерируется при начале работы агента// в SObjectizer Run-Time.void evt_start()
      {
        // Инициируем цикл опроса порта ввода-вывода.// Мы сами себе будем отсылать собственное сообщение// msg_io для выполнения очередной итерации цикла.
        so_4::api::send_msg(so_query_name(), "msg_io", 0);
      }

    void evt_io()
      {
        acquire_data();
        if(smoke_level_too_high())
          so_4::api::send_msg_safely(alarm_agent_name(), "msg_alarm",
              create_alarm_message());

        // Продолжаем цикл опроса порта ввода-вывода.
        so_4::api::send_msg(so_query_name(), "msg_io", 0);
      }
    ...
  };

В таком варианте агент Smoke_Sensor будет постоянно блокировать выделенный ему диспетчером поток внутри acquire_data. Но периодические отсылки сообщения msg_io самому себе дают возможность завершать работу SObjectizer Run-Time штатным образом, не прибегая к каким-либо фокусам. Просто в момент завершения своей работы SObjectizer запрещает отсылку новых сообщений. Поэтому, когда агент Smoke_Sensor вернется из очередного acquire_data и попробует отослать себе msg_io, сообщение никуда не уйдет. И следующего события evt_io уже не будет.

Если же агенту Smoke_Sensor требуется проводить опрос не постоянно, а через заданный промежуток времени (например, 250 миллисекунд), сообщение msg_io можно сделать периодическим, и тогда отпадет необходимость повторять его в evt_io:

        void evt_start()
  {
    // Инициируем цикл опроса порта ввода-вывода.
    so_4::api::send_msg(so_query_name(), "msg_io", 0, "",
        0, /* Без начальной задержки. */
        250 /* С темпом 250 миллисекунд. */);
  }

void evt_io()
  {
    acquire_data();
    if(smoke_level_too_high())
      so_4::api::send_msg_safely(alarm_agent_name(), "msg_alarm",
          create_alarm_message());
  }

Распределенность

В SObjectizer агенты взаимодействуют между собой только посредством асинхронных сообщений. Поэтому несложно обеспечить условия, при которых агенту-получателю сообщения совершенно неважно, откуда именно сообщение поступило. Для этого необходимо всего лишь, чтобы сообщение содержало в себе копию всех необходимых данных. В этом случае сообщение может быть сериализовано, передано по какому-то IPC (Inter Process Communication) каналу в другой процесс, десериализовано и доставлено агенту-получателю. В SObjectizer есть базовые средства для этого.

Во-первых, в SObjectizer существует т.н. SOP (SObjectizer Protocol), который определяет, как будут сериализованы сообщения агентов. SOP позволяет передавать сообщения, которые содержат поля примитивных типов (char, int, short,...) или векторов этих типов, а также std::string и сложных объектов, сериализуемых с помощью ObjESSty (http://eao197.narod.ru/objessty). Для этого нужно всего лишь описать структуру сообщения для SObjectizer в виде набора макросов:

        // Вот эта структура описывает сообщение.
        struct msg_alarm
{
  // Имя датчика, инициировавшего данное сообщение.
  std::string  m_sensor_name;
  // Текущие показания датчика.float  m_current_value;
  // Предельное значение, после которого датчик объявляет тревогу.float m_max;
};

// А вот так это сообщение должно быть описано для SObjectizer.
SOL4_MSG_START(msg_alarm, msg_alarm)
  SOL4_MSG_FIELD(m_sensor_name)
  SOL4_MSG_FIELD(m_current_value)
  SOL4_MSG_FIELD(m_max)
SOL4_MSG_FINISH()

Такое описание позволяет SObjectizer на стороне отправителя взять значения полей m_sensor_name, m_current_value и m_max из объекта-сообщения и сериализовать их в коммуникационный канал. А на принимающей стороне SObjectizer создает объект типа msg_alarm и восстанавливает значения его полей, после чего доставляет созданный объект получателю.

Во-вторых, SObjectizer вводит понятие коммуникационного канала (экземпляр конкретного IPC-соединения или сессии). Коммуникационные каналы обслуживают специальные транспортные агенты. Например, для организации взаимодействия двух процессов по TCP/IP в одном из них нужно объявить транспортного агента, который создаст серверный TCP/IP-сокет, а во втором процессе — объявить транспортного агента, который создаст клиентский TCP/IP-сокет. Далее эти агенты сами будут устанавливать и обслуживать TCP/IP-соединения, объявляя каждую новую сессию отдельным коммуникационным каналом. Например, вот так выглядит создание транспортного агента для серверного сокета:

so_4::rt::comm::a_srv_channel_t a_channel(
  "a_channel",
  so_4::socket::channels::create_server_channel(ip_address));

а вот так — создание транспортного агента для клиентского сокета:

so_4::sop::std_filter_t * filter = so_4::sop::create_std_filter();
filter->insert(a_common_t::agent_name());

so_4::rt::comm::a_cln_channel_t a_channel(
  "a_channel",
  so_4::socket::channels::create_client_factory(ip_address),
  filter,
  // Вот этот объект будет указывать агенту повторять попытки// установления соединения каждые 5 секунд, и инициировать// повторное соединение после разрыва без задержек.
  so_4::rt::comm::a_cln_channel_t::
    create_def_disconnect_handler(5000, 0));

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

В SObjectizer существует понятие т.н. глобального агента. Это агент, который владеет только сообщениями, но не может иметь ни состояний, ни событий. Он даже регистрируется в SObjectizer особым образом. Зато его сообщения коммуникатор отслеживает и рассылает в доступные коммуникационные каналы.

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

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

        class Fire_Alarm : public so_4::rt::agent_t
  {
  ...
  public :
    struct msg_alarm { ... };
  ...
  };

SOL4_CLASS_START(Fire_Alarm)

  SOL4_MSG_START(msg_alarm, Fire_Alarm::msg_alarm)
    ...
  SOL4_MSG_FINISH()

SOL4_CLASS_FINISH()

и зарегистрировать его в каждом из взаимодействующих процессов:

so_4::api::make_global_agent(
  // Имя агента."a_fire_alarm",
  // Имя его типа. Оно необходимо SObjectizer, чтобы определить,// какими сообщениями будет владеть данный агент."Fire_Alarm");

после чего агент Smoke_Sensor будет отсылать сообщение этого агента:

        void Smoke_Sensor::evt_io()
{
  acquire_data();
  if(smoke_level_too_high())
    so_4::api::send_msg_safely("a_fire_alarm", "msg_alarm",
      new Fire_Alarm::msg_alarm(...));
}

а агент Alarm_Handler будет подписываться на это сообщение и обрабатывать его:

        void Alarm_Handler::so_on_subscription()
{
  so_subscribe("evt_alarm", "a_fire_alarm", "msg_alarm");
  ...
}

void Alarm_Handler::evt_alarm(const Fire_Alarm::msg_alarm & cmd)
{
  ...
}

Процессам придется также создать у себя соответствующих транспортных агентов. И все. Всем остальным будет заниматься SObjectizer.

Важно здесь то, что ни агент Smoke_Sensor, ни агент Alarm_Handler не знают о том, где они расположены по отношению друг к другу. Они могут существовать как в одном, так и в разных процессах. Схема их взаимодействия не изменится. Однако, при желании, агент Alarm_Handler сможет узнать, откуда именно поступило сообщение msg_alarm — SObjectizer может сообщить, из какого коммуникационного канала сообщение было получено. И более того, SObjectizer позволяет отослать сообщение в конкретный коммуникационный канал. Тем самым SObjectizer делает возможным организацию peer-to-peer взаимодействия:

        void Alarm_Handler::evt_alarm(
  const so_4::rt::event_data_t & event_data,
  const Fire_Alarm::msg_alarm & cmd)
{
  ...
  // Отсылаем этому датчику подтверждение того, что его// сигнал был принят и обработан. Подтверждение отсылается// в тот же канал, откуда пришло сообщение.
  so_4::api::send_msg_safely(
    // Вот идентификатор канала, из которого сообщение поступило.
    event_data.channel(),
    "a_fire_alarm", "msg_alarm_ack",
    new Fire_Alarm::msg_alarm_ack(...));
}

Примечательно, что такой механизм будет работать, даже если Smoke_Sensor и Alarm_Handler будут находиться внутри одного процесса — в этом случае в качестве идентификатора коммуникационного канала будет выступать специальный идентификатор localhost, который показывает, что сообщение было порождено внутри процесса, а не получено извне.

Нужно отметить, что поддержка распределенности на основе глобальных агентов в SObjectizer является относительно примитивным инструментом, чем-то напоминающим Parallel Virtual Machine (PVM, http://www.csm.ornl.gov/pvm/pvm_home.html) или Message Passing Interface (MPI, http://www.mpi-forum.org). Однако в некоторых случаях и его вполне достаточно для удобной реализации распределенных приложений. А для более сложных случаев на основе этого механизма можно делать более мощные инструменты. Так, например, в компании Интервэйл на основе глобальных агентов реализован инструмент mbapi (Message Box API), который поддерживает более сложные структуры данных, более изощренные методы подписки и доставки сообщений (включая перехват, маршрутизацию, балансировку нагрузки и др.), но реализован исключительно с использованием штатных механизмов SObjectizer.

Надежность?

Мне бы очень хотелось сказать, что SObjectizer позволяет писать более надежные приложения, но это не соответствовало бы реальному положению вещей. На самом деле, при использовании SObjectizer нужно учитывать один важный фактор, который непосредственным образом сказывается на надежности — асинхронность взаимодействия агентов. В SObjectizer нет понятия синхронной доставки сообщения, отправитель совершенно не знает, в какой момент времени подписчик сообщения сможет обработать отправленное сообщение — это может произойти еще до возврата из send_msg или через несколько часов после отсылки сообщения, или не произойти вовсе. А при широковещательной отсылке даже неизвестно, сколько именно получателей обработают данное сообщение.

Но самое неприятное то, что отправитель сообщения даже не может быть уверен в том, что отправленное им сообщение вообще кем-то будет получено. Если вернуться к примеру с пожарной сигнализацией, то представьте себе ситуацию, когда Smoke_Sensor отсылает сообщение msg_alarm, но его никто не обрабатывает! Причины могут быть разные: агент Alarm_Handler находится не в том состоянии, связь с процессом, в котором работает Alarm_Handler, была потеряна, после отсылки сообщения агент Alarm_Handler был дерегистрирован и просто не успел получить сообщение и т.д. Выглядит ужасно, не так ли? Ох уж эта асинхронность, с ней ни в чем нельзя быть уверенным!

А гарантирует ли что-нибудь синхронность? Например, пусть объект Smoke_Sensor синхронно вызывает метод alarm_detected объекта Alarm_Handler. Можно ли быть уверенным, что Alarm_Handler в этом случае 100% гарантирует должную реакцию? Ведь за долю секунды до этого у Alarm_Handler-а кто-то еще мог вызвать метод shutdown, после чего всем остальным Alarm_Handler будет возвращать код ошибки вместо того, чтобы реагировать на событие. Или представим себе, что кто-то синхронно записывает в сокет сообщение. Может ли он быть уверен, что сообщение будет получено на другой стороне, если операция write возвращает успешный код возврата? Нет, не может. И таких примеров можно привести множество.

Для меня вывод из этого состоит в том, что получение надежности за счет использования только синхронности — это иллюзия. Что действительно дает синхронность, так это возможность сразу же после возврата из синхронного вызова получить результат операции. Т.е., если кто-то уже остановил Alarm_Handler, то после возврата из alarm_detected будет понятно, что Alarm_Handler ничего не сделал. Кроме вполне естественного вопроса о том, что Smoke_Sensor делать дальше, есть еще один вопрос: а как быстро вообще произойдет возврат из alarm_detected? Если Smoke_Sensor и Alarm_Handler работают на одной машине и код alarm_detected тривиален, то быстро. А что, если Smoke_Sensor обращается к удаленному Alarm_Handler через интерфейс RPC (Remote Procedure Call)? Ведь тогда вызов alarm_detected может затянуться на довольно значительное время. И, что еще более обидно, результатом вызова может оказаться код возврата, свидетельствующий об истечении тайм-аута. Тогда вообще не понятно, дошел ли запрос до Alarm_Handler или нет.

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

С данным вопросом непосредственно связана одна из проблем, которая часто возникает при недостаточном опыте использования SObjectizer: попытка представить мелкие объекты агентами и реализовать их взаимодействие через сообщения. Это приводит к большим трудозатратам при попытках обеспечить их надежную работу в условиях асинхронности. В таких случаях гораздо проще использовать обычные, синхронные вызовы, а в виде агентов представлять достаточно большие, относительно независимые друг от друга подсистемы приложения. Вот тогда решение действительно может стать надежнее, т.к. каждая из подсистем будет ожидать возможных проблем в других подсистемах и пытаться преодолевать их за счет повторных посылок сообщений после тайм-аута. Трудоемкость создания таких решений будет вполне приемлемой вследствие небольшого количества возможных типов взаимодействий между агентами.

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

Ложка дегтя

SObjectizer является моим любимым инструментом. Я уже очень давно участвую в разработке SObjectizer и несколько последних лет программирую только с использованием SObjectizer. Но несмотря на это (а может и наоборот, именно благодаря этому) я вижу в SObjectizer ряд проблем и недостатков. На некоторых из них хотелось бы остановиться подробнее, т.к., по моему мнению, от их разрешения во многом зависит будущее SObjectizer. Условно говоря, все недостатки можно разделить на три группы — технические, социально-политические и организационные.

Технические проблемы

Самой главной технической проблемой на данный момент я считаю необходимость делать большое количество описаний в C++-коде для агентов. Нужно описать класс агента, его сообщения и события в виде обычных C++-конструкций (т.н. описание агента для C++). Затем с помощью макросов нужно сделать описание агента для SObjectizer. Далее нужно сделать реализацию метода so_on_subscription для того, чтобы подписать события агента. Ну и затем агентов нужно создавать и регистрировать (хотя по сравнению с предыдущими пунктами это уже мелочи). К сожалению, все эти операции приходится производить вручную, что чревато ошибками. Которые время от времени случаются. Например, часто забывают добавить событие в список разрешенных событий какого-то состояния. Или, не менее часто, забывают подписать событие.

Эта проблема является следствием двух причин: вначале, при выпуске первой реинкарнированой версии SObjectizer не было времени на создание какого-то декларативного описания агентов, его транслятора и генератора C++-кода. Вместо этого были использованы штатные средства C++ — препроцессор, анонимные пространства имен, шаблоны, перегрузка функций. Ну а дальше уже требовалась 100% совместимость с ранее написанным кодом, не было возможности осуществить революционный переход на какой-то иной способ описания агентов.

Поэтому есть желание как-то упростить программирование в SObjectizer. Например, разбирать C++-описание класса агента и автоматически выделять оттуда описания агентов и их событий, а затем на основании этой информации генерировать описание агента для SObjectizer. Но, кроме технической сложности разбора C++-конструкций, приходится учитывать еще и то, что SObjectizer нужно больше информации, чем можно выразить в синтаксисе C++ (например, списки состояний и разрешенных в них событий, приоритеты событий, имена инцидентов событий и др.). Еще один вариант — создавать описания агента на XML (с применением XSD, чтобы задействовать возможности специализированных редакторов XML), а затем из них генерировать необходимые фрагменты C++-кода. Окончательного решения о том, как сократить количество описаний для SObjectizer, пока нет.

Социально-политические проблемы

Главная политическая проблема SObjectizer в том, что SObjectizer существует только на C++ и только для C++. В частности, возможности SObjectizer для создания распределенных приложений можно использовать исключительно в С++. Конечно, SObjectizer не мешает использовать в этом же приложении иные способы взаимодействия (CORBA, COM, SOAP, XML-RPC). Но и не помогает. А если в приложение встраивается ручная поддержка CORBA или SOAP, то встает вопрос: “К чему тогда вообще использовать SObjectizer?” И ответ на этот вопрос, особенно когда требуется взаимодействие с написанными на других языках приложениями, часто бывает не в пользу SObjectizer.

Еще одна проблему SObjectizer унаследовал от C++. В последнее время у C++ появились очень серьезные конкуренты. С одной стороны, это Java и .Net/C# с огромным количеством готовых библиотек и IDE нового поколения. Очень многих программистов от C++ отталкивает отсутствие настолько же удобных IDE (с автодополнением, автоподсказкой, поддержкой рефакторинга) как IDEA и Eclipse для Java, или VisualStudio+ReSharper для C#. В С++ с этим дела обстоят неважно. A у SObjectizer еще хуже — ведь сейчас нет ни одной IDE, способной поддерживать специфические особенности SObjectizer (хотя бы так, как VisualStudio помогла использовать MFC).

С другой стороны, популярными становятся такие динамические языки, как Perl, Python и Ruby, для которых также существует большое количество готовых библиотек, и программирование на которых существенно отличается от программирования на статически-типизированных языках, вроде C++/Java/C#.

Все это заставляет задуматься о том, в какой нише C++ продолжит свое безбедное существование. И о том, с кем лучше “подружить” SObjectizer? С Java? Но у Java свой, вполне самодостаточный, мир. Вряд ли EJB-приложения будут нуждаться в интеграции с C++-модулями на SObjectizer.

Может быть .Net/C#? Может быть. Платформа новая, активно развивающаяся. Например, можно добавить в SObjectizer возможность общаться с .Net-приложениями. Или вообще перенести SObjectizer на C#.

Но более перспективным, на мой взгляд, является интеграция SObjectizer с динамическими языками, особенно с Ruby и Python. Опять же, возможно как предоставление скриптам на Ruby (Python) возможности взаимодействия с SObjectizer, так и перенос SObjectizer на эти языки. Особенно заманчивой представляется реализация SObjectizer на Ruby с учетом большого потенциала Ruby в области метапрограммирования.

Организационные проблемы

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

Terra Incognita

Ниша SObjectizer

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

Сейчас SObjectizer используется для создания приложений в области телекоммуникаций. На основе SObjectizer в компании Интервэйл разработана и успешно эксплуатируется распределенная система маршрутизации SMS-трафика. Характерной особенностью этой предметной области является наличие большого потока мелких транзакций, что прекрасно ложится на агентную модель и асинхронный обмен сообщениями между агентами.

Может быть, будущее SObjectizer в задачах, требующих передачи множества мелких сообщений между несколькими компонентами (агентами)? Вполне возможно. В каких предметных областях существуют такие задачи? Телекоммуникации? Управление технологическими процессами и оборудованием? Имитационное моделирование? Компьютерные игры? Распределенные вычисления?

Чтобы ответить на эти вопросы, нужно быть специалистом в этих областях, но я им не являюсь. Поэтому, к сожалению, я не могу сказать: “Если вы занимаетесь IP-телефонией, то SObjectizer является для вас идеальным решением в таких-то и таких-то случаях”. Я могу лишь рассказать об особенностях программирования с использованием SObjectizer, и о том, чем это может быть выгодно. Но, может быть, заинтересованные читатели (если таковые появились) подскажут для SObjectizer новые потенциальные сферы применения?

Найдется ли место для синхронности?

Как показывает практика, после первого знакомства с SObjectizer возникает вопрос: “Асинхронность — это хорошо, но как в SObjectizer обстоят дела с поддержкой синхронного взаимодействия?” Коротко говоря, неважно. Нет в SObjectizer поддержки синхронности.

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

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

Если же получение синхронных сообщений для агента-подписчика будет выглядеть как получение асинхронного сообщения, то в каком контексте должен происходить вызов обработчика события? Делать ли это внутри sync_msg в контексте агента-отправителя? Тогда агент-получатель должен быть готов к работе в разных контекстах и поэтому должен сам заботится о своей синхронизации. Может быть, задействовать обычный механизм диспетчеризации, запускать событие обработчика в контексте его рабочего потока, а возврат из sync_msg отложить до окончания обработки события? Заманчиво, ведь тогда обработка синхронных и асинхронных сообщений для агента вообще ничем не будут отличаться. Но тогда отправитель синхронного сообщения должен работать в контексте другого потока. А это значит, что синхронность будет доступна не всем агентам и не со всеми диспетчерами. Кроме того, что делать, если агент-обработчик захочет отослать ответное сообщение отправителю также через sync_msg?

Еще интереснее вопросы о том, могут ли быть реализованы такие вещи, как широковещательная синхронная отсылка сообщения? Можно ли синхронно отсылать сообщения глобальных агентов? Если да, то нужно ли ожидать, пока его обработают получатели на других узлах сети?

Приведенные выше вопросы — это только верхушка айсберга. Вероятно, все эти проблемы с синхронностью вызваны тем, что SObjectizer изначально создавался исключительно для асинхронного режима работы. Но является ли архитектура SObjectizer непреодолимым препятствием для внедрения синхронности в SObjectizer? Я надеюсь, что нет. Просто пока нет оригинальной идеи о том, как наиболее естественно и безболезненно это сделать.

Заключение

Разработка сначала SCADA Objectizer, а затем SObjectizer была сложным, но крайне интересным, увлекательным, захватывающим и приятным занятием. Я получал удовольствие, когда создавал SObjectizer, и получаю удовольствие сейчас, когда программирую с использованием SObjectizer. Я рад, что оказался в компании Интервэйл, и что нам удалось сделать такой уникальный инструмент.

Но чем больше я думаю о будущем SObjectizer, тем больше прихожу к мысли, что его будущее зависит от выхода за пределы компании Интервэйл. А для этого нужен хотя бы небольшой круг программистов, которых SObjectizer заинтересует. Но для этого о SObjectizer нужно рассказать. Я надеюсь, что данная статья как раз и станет таким рассказом. И, если кого-то это повествование заинтересовало, то я готов его продолжить — связаться со мной можно по e-mail: mailto:eao197@intervale.ru.

Благодарности

Автор выражает благодарность Василию Воробьеву, Василию Гайдукову, Андрею Лабычу, Алексею Ровдо, Андрею Степачеву, Алексею Черемхину за помощь в подготовке данной статьи.


Эта статья опубликована в журнале RSDN Magazine #4-2005. Информацию о журнале можно найти здесь