четверг, 1 сентября 2016 г.

[prog.c++] Нововведения в SO-5.5.18: глобальные lock_factories

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

Эта функциональность появилась в версиях 5.5.10 и 5.5.11. Тогда же были реализованы два типа lock_factories:

  • combined_lock_factory создает комбинированные синхронизирующие объекты. Такой комбинированный синхронизирующий объект состоит из двух частей -- spin-lock-а и обычного mutex-а. Захват подобного объекта производится хитрым образом: сначала делается попытка захватить spin-lock с вызовом std::this_thread::yield между неудачными попытками. Если захватить spin-lock таки не удается, то делается попытка захватить mutex. Тем самым при затянувшемся ожидании нить, которая пытается захватить синхронизирующий объект, дает возможность ОС усыпить себя до тех пор, пока ресурс не освободится. Такие комбинированные синхронизирующие объекты отлично показывают себя в нагруженных сценариях, когда идет интенсивный обмен сообщениями и никто подолгу не ждет, когда же для него появится какая-то работа;
  • simple_lock_factory создает синхронизирующие объекты на базе обычного mutex-а. Т.е. нить, пытающая захватить ресурс, которым уже кто-то владеет, просто сразу же заснет на ожидании освобождения mutex-а. Когда mutex освободится ОС разбудит спящую нить и толкнет ее на выполнение. Такие синхронизирующие объекты более дороги, но зато приложение практически не потребляет ресурсов, если сейчас в нем очереди сообщений пусты (никто не гоняет ядра зря на spin-lock-ах).

По умолчанию везде используется combined_lock_factory. Если это не подходит, то в свойствах очереди сообщений диспетчера можно указать использование simple_lock_factory.

До версии 5.5.18 заменять combined_lock_factory на simple_lock_factory можно было только на уровне отдельного диспетчера. Т.е. при создании диспетчера и в его queue_traits указывается simple_lock_factory. Что не очень удобно, если приложение создает не один диспетчер, а несколько несколько (особенно, если их счет идет на десяток-другой): нужно не забыть правильно настроить queue_traits для каждого из них.

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

В версии 5.5.18 этот недостаток был устранен за счет возможности задать дефолтные lock_factories для всего SObjectizer Environment. Т.е., если приложение хочет, чтобы все диспетчеры в своих очередях по умолчанию использовали только simple_lock_factory, то начиная с версии 5.5.18 это можно задать через environment_params_t. Например:

so_5::launch( []( so_5::environment_t & env ) {
      ... // Some initial actions.
   },
   []( so_5::environment_params_t & params ) {
      // Use simple_lock_factory for event queues by default.
      params.queue_locks_defaults_manager(
            so_5::make_defaults_manager_for_simple_locks() );
      ...
   } );

После этого при создании диспетчеров будет использоваться следующая логика:

  1. Если программист в queue_traits для диспетчера задал конкретный lock_factory, то при создании очередей этого диспетчера будет использоваться этот конкретный lock_factory. Глобальные настройки SObjectizer Environment игнорируются.
  2. В противном случае при создании очередей этого диспетчера будет использоваться lock_factory из параметров SObjectizer Environment. В показанном выше примере это будет simple_lock_factory.

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

Если при старте SObjectizer Environment программист явным образом не задавал queue_locks_defaule_manager, то в качестве умолчаний будет использоваться combined_lock_factory. Т.е. дефолтное поведение диспетчеров и очередей сообщений в SObjectizer в версии 5.5.18 не меняется.

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