понедельник, 26 декабря 2016 г.

[prog.thoghts] Еще несколько слов на тему однопоточного SObjectizer Environment

Тема однопоточного SObjectizer Environment сидит в голове уже довольно давно. К сожалению, она не так проста, как это представлялось ранее. Поэтому руки дошли только сейчас.

Почему тема не так проста. Имеет смысл рассматривать три ситуации:

  1. Используется многопоточный SOEnv. Не суть важно, есть ли в приложении другие рабочие потоки или нет, просто из-за того, что сами агенты внутри SOEnv могут работать на разных потоках, нужно защищать внутренности SOEnv, диспетчеров и самих агентов от многопоточного доступа. Т.е. где-то будут использоваться атомарные счетчики, где-то спин-локи, где-то мутексы и т.д. и т.п.
  2. Используется однопоточный SOEnv, но внутри многопоточного приложения. Т.е. все агенты работают внутри SOEnv, но кроме них в приложении есть еще и другие активности. Поэтому сообщение агенту может прилететь не только из самого SOEnv, но и извне SOEnv. Поэтому потроха SOEnv и агентов так же должны защищаться (следовательно, остаются те же самые атомарные счетчики, спин-локи, мьютексы и т.д. и т.п.). Так что в этой ситуации реализация самих агентов мало чем отличается от реализации агентов для случая многопоточного SOEnv.
  3. Используется однопоточный SOEnv внутри однопоточного же приложения. Т.е. когда запускается SOEnv, в приложении больше ничего не работает. Это означает, что агент может получить сообщение только из этого же самого SOEnv. А это устраняет необходимость защиты потрохов SOEnv и агентов от многопоточного доступа. Т.е. не нужны ни атомарные счетчики, ни спин-локи, ни мутексы...

Пока для версии 5.5.19 рассматривается вариант сделать однопоточный SOEnv только для второго сценария, т.е. для случая, когда однопоточный SOEnv работает внутри многопоточного приложения. В этом случае можно не только переиспользовать уже имеющийся код, но и нет риска внести несовместимость между версиями на уровне исходных текстов. Ибо сейчас мне кажется, что реализовать SO-5, который бы эффективно работал во всех трех ситуациях, возможно только за счет активного использования шаблонов.

Например, чтобы базовый тип агента, so_5::agent_t, был шаблоном приблизительно такого вида:

templatetypename Env = so_5::mt_env::environment_t >
class agent_t {
public :
   ...
   Env & so_environment() { return *m_env; }
private :
   Env * m_env;
   ... // Какие-то внутренности, которые зависят от типа Env.
};

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

class my_agent_t : public so_5::agent_t<> {
   ... // Начинка прикладного агента.
};

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

class st_only_agent_t : public so_5::agent_t< so_5::st_env::environment_t > {
   ... // Начинка прикладного агента.
};

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

templatetypename Env >
class universal_agent_t : public so_5::agent_t< Env > {
   ... // Начинка прикладного агента.
};

Вот в случае с шаблонной реализацией агентов более-менее понятно, как играть с деталями реализации so_5::agent_t, чтобы для каждого типа окружения (многопоточного или однопоточного) в агенте были все необходимые внутренности (а так же отсутствовало все лишнее). Если же пытаться обходиться без шаблонов, то, боюсь, все уходит в сторону дополнительной косвенности. Т.е. очевидным образом напрашивается PImpl, где внутренности агента будут создаваться динамически при регистрации агента в SOEnv и конкретный тип этих внутренностей будет определяться типом SOEnv.

Вот эта самая косвенность мне и не нравится. Поэтому шаблоны выглядят предпочтительнее.

Но если агенты становятся шаблонами, параметризуемыми через SOEnv, то с большой долей вероятности и все остальные части SObjectizer-а так же окажутся шаблонами. Например, mbox-ы так же должны быть шаблонами, т.к. для многопоточных SOEnv в них должны быть примитивы синхронизации, а для однопоточных SOEnv это не нужно. Диспетчеры так же нужно будет оформить так, чтобы какой-нибудь thread_pool диспетчер мог применяться для многопоточного SOEnv, но не мог для однопоточного (и чтобы это проверялось в compile-time). Да и сам so_5::environment_t вполне может быть шаблоном.

Так что это прямая дорога к тому, чтобы SO-5 перешел в категорию header-only библиотек. Что может и не есть плохо, но на данный момент это слишком стремный шаг.

Поэтому пока речь идет о том, чтобы в SO-5.5.19 сделать поддержку многопоточного SOEnv и однопоточного SOEnv в многопоточном приложении. Посмотреть, что из этого выйдет. А потом, если будет такая необходимость, решить что делать с однопоточным SOEnv для однопоточных приложений: переводить ли SO-5 в категорию header-only библиотек или идти каким-то другим путем.

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


Знаю, что можно попробовать сохранить совместимость на уровне исходных текстов за счет вот таких приемов:

namespace so_5 {

templatetypename Env >
class agent_template { ... };

using agent_t = agent_template< so_5::mt_env::environment_t >;

templatetypename Env >
class mbox_template { ... };

using mbox_t = mbox_template< so_5::mt_env::environment_t >;

...
}

Что позволит сохранить работоспособность уже написанного кода вида:

class my_agent : public so_5::agent_t
{
public :
   my_agent( context_t ctx, so_5::mbox_t parent )
      :  so_5::agent_t{ std::move(ctx) }
      ,  m_parent{ std::move(parent) }
   {}

   ...
private :
   const so_5::mbox_t m_parent;
   ...
};

Очевидно, что так и придется делать. Только сильно подозреваю, что если прямо сейчас делать на шаблонах однопоточный SOEnv для однопоточных приложений, то этих трюков просто не хватит для сохранения совместимости.

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