Для чего мы делали свой акторный фреймворк и что из...
TRANSCRIPT
C++ Russia 2017
Для чего мы делали свой акторный фреймворк и что из
этого вышло?
Евгений Охотников
Давным-давно, в далекой-далекой...
1994-й год.
Гомель.
КБ Системного Программирования.
Отдел АСУТП.
Попытка сделать объектно-ориентированную SCADA-систему.
SCADA - Supervisory Control And Data Acquisition
3
О SCADA-системах в двух словах
4
http://www.scadasoftware.net/
Объектная SCADA. Зачем?
Традиционный подход SCADA-систем в те времена:
● набор нумерованных или именованных каналов ввода-вывода (тегов);● слабая структуризация и почти отсутствие декомпозиции;● трудозатраты растут с увеличением числа тегов;● трудозатраты еще быстрее растут по мере усложнения логики
автоматизируемого техпроцесса.
Мы хотели устранить это за счет декомпозиции на объекты, обладающие состоянием и поведением.
5
Объектная SCADA. Что получилось?
Агенты:
● конечные автоматы с явно выделенными состояниями;● состояния агентов видны снаружи.
6
Объектная SCADA. Что получилось?
Взаимодействие через асинхронные сообщения:
● прицел на soft real-time;● распространение информации как в режиме 1:1, так и в
режиме 1:N.
7
Объектная SCADA. Что получилось?
Диспетчер.
Отвечал за обработку очередей сообщений с учетом приоритетов и требований soft real-time.
8
Объектная SCADA. Итог
SCADA Objectizer.
1998-й год.
Опробован в реальном проекте.
Прекратил свое существование в начале 2000-го :(
9
Предпосылки к перерождению
Начало 2002-го.
Компания "Интервэйл".
Двое участников разработки SCADA Objectizer.
Небольшая GUI-программа для управления подключенными к ПК устройствами...
10
Четвертое пришествие
Апрель 2002-го года.
SObjectizer = SCADA + Objectizer.
SObjectizer-4 (в четвертый раз все сначала).
11
Этапы большого пути
Май 2002-го: начало использования SO-4 в разработке.
Март 2006-го: перевод SO-4 в категорию OpenSource проектов под BSD-лицензией.
Сентябрь 2010-го: начало работ над SObjectizer-5.
Май 2013-го: публичный релиз SO-5 под BSD-лицензией.
Июль 2013-го: SObjectizer начал жить независимо от "Интервэйл".
12
На данный момент...
Последняя стабильная версия SObjectizer-5.5.18 – это:
● 25KLOC кода (+28KLOC кода в тестах +10KLOC кода в примерах);
● работа на платформах Windows, Linux, FreeBSD, MacOS, Android (через CrystaX NDK);
● поддержка компиляторов VC++ 12.0-14.0, GCC 4.8-6.3, clang 3.5-3.9;
● документация, презентации, статьи;● отсутствие больших ломающих изменений с октября 2014-го.
13
Как перестать бояться и...
...полюбить многопоточность?
Голые thread, mutex и condition_variable – это пот, кровь и боль.
Асинхронный обмен сообщениями рулит!И бибикает :)
Ибо shared nothing и вот этот вот все.
15
Бонусы асинхронного обмена сообщениями
● у каждого агента свое изолированное состояние (принцип shared nothing), упрощает жизнь в многопоточном коде;
● обмен сообщениями – естественный подход к решению некоторых типов задач;
● слабая связность между компонентами;● очень простая работа с таймерами (отложенные и
периодические сообщения);● низкий порог входа для новичков.
16
В SObjectizer есть сообщения
Все взаимодействие между агентами в SObjectizer идет только через асинхронные сообщения.
Сообщения это объекты. Тип сообщения наследуется от so_5::message_t.
18
Выглядят сообщения вот так:struct request : public so_5::message_t{ std::int64_t id_; std::map<std::string, std::string> params_; std::vector<std::uint8_t> payload_; std::chrono::steady_clock::timepoint deadline_;
request( std::int64_t id, std::map<std::string, std::string> params, std::vector<std::uint8_t> payload, std::chrono::steady_clock::timeout deadline) : id_(id), params_(std::move(params)), payload_(std::move(payload)), deadline_(deadline) {}};
struct get_status : public so_5::signal_t {};
19
Отсылаются сообщения вот так:// Безотлагательная отсылка сообщения.so_5::send<request>(target, // Все это через perfect-forwarding идет в конструктор request-а. make_id(), make_params(), make_payload(), calculate_deadline());
// Безотлагательная отсылка сигнала.so_5::send<get_status>(target);
// Отсылка сигнала с задержкой на 250ms.so_5::send_delayed<get_status>(target, std::chrono::milliseconds(250));
// Периодическая отсылка сигнала со стартовой задержкой в 250ms// и периодом повтора в 750ms.auto timer = so_5::send_periodic<get_status>(target, std::chrono::milliseconds(250), std::chrono::milliseconds(750));
20
Что такое Target для send?
В "традиционной" Модели Акторов адресатом сообщения является актор.
В SObjectizer сообщения отсылаются в "почтовый ящик".
Почтовый ящик в SObjectizer называется mbox.
21
Подписка на сообщение из mbox-а
Для получения сообщения из mbox-а нужно выполнить подписку. Только после этого сообщения будут доставляться агенту.
Подписку можно отменить. Сообщения доставляться перестанут.
Подписки агента автоматически удаляются, когда агент уничтожается.
Сообщения диспетчируются по типу, а не по содержимому.
22
Multi-Producer/Single-Consumer Mbox
Кто угодно может оправить. Подписаться может только один агент.MPSC-mbox создается для каждого агента автоматически. Использование MPSC-mbox-ов дает максимально близкое приближение к Модели Акторов.
24
Multi-Producer/Multi-Consumer Mbox
Кто угодно может оправить. Получат все подписчики.
MPMC-mbox нужно создавать вручную (можно с именем, можно без).Простейший вариант модели Publish/Subscribe.
25
В SObjectizer есть диспетчеры
Именно диспетчер определяет где и когда агент будет обрабатывать свои сообщения.
Каждый агент в SObjectizer должен быть привязан к конкретному диспетчеру.
В приложении может быть запущено сразу несколько диспетчеров.
26
Примеры диспетчеров
one_thread: все агенты на одной и той же нити, все агенты используют одну очередь сообщений.
active_obj: у каждого агента своя собственная нить, у каждого агента собственная очередь сообщений.
adv_thread_pool: агенты на группе нитей, агент может мигрировать с одной нити на другую, события одного агента могут быть запущены параллельно, если они отмечены как thread_safe. Очереди сообщений могут быть персональными или общими для нескольких агентов.
27
Больше диспетчеров, хороших и разных
Всего в SObjectizer-5 "из коробки" доступно восемь типов диспетчеров:
active_groupactive_obj
adv_thread_poolone_thread
prio_dedicated_threads::one_per_prioprio_one_thread::quoted_round_robin
prio_one_thread::strictly_orderedthread_pool
28
В SObjectizer есть агенты
Как правило, реализуются как объекты классов, унаследованных от so_5::agent_t.
class hello_world final : public so_5::agent_t {public : using so_5::agent_t::agent_t;
virtual void so_evt_start() override { std::cout << "Hello, World!" << std::endl; so_deregister_agent_coop_normally(); }};
30
Агенты – это конечные автоматы
1. Мы не поддерживаем агентов в виде сопрограмм. Поэтому должен использоваться механизм callback-ов, чтобы отобразить N агентов на M рабочих нитей.
2. Ноги у SObjectizer растут из мира АСУТП, там конечные автоматы – это обычное дело.
3. Когда есть ярко выраженные состояния и разное поведение в каждом из состояний конечный автомат удобнее, чем вызовы receive с последующим выбором обработчика.
31
Агент blinking_led (код)class blinking_led final : public so_5::agent_t { state_t off{ this }, blinking{ this }, blink_on{ initial_substate_of{ blinking } }, blink_off{ substate_of{ blinking } };
public : struct turn_on_off : public so_5::signal_t {};
blinking_led( context_t ctx ) : so_5::agent_t{ std::move(ctx) } { this >>= off;
off.just_switch_to< turn_on_off >( blinking ); blinking.just_switch_to< turn_on_off >( off );
blink_on .on_enter( []{ std::cout << "ON" << std::endl; } ) .on_exit( []{ std::cout << "off" << std::endl; } ) .time_limit( std::chrono::milliseconds{1500}, blink_off ); blink_off .time_limit( std::chrono::milliseconds{750}, blink_on ); }};
33
Агент blinking_led (пояснения)class blinking_led final : public so_5::agent_t { state_t off{ this }, blinking{ this }, blink_on{ initial_substate_of{ blinking } }, blink_off{ substate_of{ blinking } };
public : struct turn_on_off : public so_5::signal_t {};
blinking_led( context_t ctx ) : so_5::agent_t{ std::move(ctx) } { this >>= off;
off.just_switch_to< turn_on_off >( blinking ); blinking.just_switch_to< turn_on_off >( off );
blink_on .on_enter( []{ std::cout << "ON" << std::endl; } ) .on_exit( []{ std::cout << "off" << std::endl; } ) .time_limit( std::chrono::milliseconds{1500}, blink_off ); blink_off .time_limit( std::chrono::milliseconds{750}, blink_on ); }};
34
Определение двух верхнеуровневых состояний.
Агент blinking_led (пояснения)class blinking_led final : public so_5::agent_t { state_t off{ this }, blinking{ this }, blink_on{ initial_substate_of{ blinking } }, blink_off{ substate_of{ blinking } };
public : struct turn_on_off : public so_5::signal_t {};
blinking_led( context_t ctx ) : so_5::agent_t{ std::move(ctx) } { this >>= off;
off.just_switch_to< turn_on_off >( blinking ); blinking.just_switch_to< turn_on_off >( off );
blink_on .on_enter( []{ std::cout << "ON" << std::endl; } ) .on_exit( []{ std::cout << "off" << std::endl; } ) .time_limit( std::chrono::milliseconds{1500}, blink_off ); blink_off .time_limit( std::chrono::milliseconds{750}, blink_on ); }};
35
Определение двух вложенных состояний для состояния blinking.
Подсостояние blink_on является начальным подсостоянием.
Агент blinking_led (пояснения)class blinking_led final : public so_5::agent_t { state_t off{ this }, blinking{ this }, blink_on{ initial_substate_of{ blinking } }, blink_off{ substate_of{ blinking } };
public : struct turn_on_off : public so_5::signal_t {};
blinking_led( context_t ctx ) : so_5::agent_t{ std::move(ctx) } { this >>= off;
off.just_switch_to< turn_on_off >( blinking ); blinking.just_switch_to< turn_on_off >( off );
blink_on .on_enter( []{ std::cout << "ON" << std::endl; } ) .on_exit( []{ std::cout << "off" << std::endl; } ) .time_limit( std::chrono::milliseconds{1500}, blink_off ); blink_off .time_limit( std::chrono::milliseconds{750}, blink_on ); }};
36
Перевод агента в то состояние, в котором он должен начать свою работу.
Кому не нравится перегрузка >>= для смены состояния агента, для тех есть альтернативный синтаксис:
off.activate();so_change_state(off);
Агент blinking_led (пояснения)class blinking_led final : public so_5::agent_t { state_t off{ this }, blinking{ this }, blink_on{ initial_substate_of{ blinking } }, blink_off{ substate_of{ blinking } };
public : struct turn_on_off : public so_5::signal_t {};
blinking_led( context_t ctx ) : so_5::agent_t{ std::move(ctx) } { this >>= off;
off.just_switch_to< turn_on_off >( blinking ); blinking.just_switch_to< turn_on_off >( off );
blink_on .on_enter( []{ std::cout << "ON" << std::endl; } ) .on_exit( []{ std::cout << "off" << std::endl; } ) .time_limit( std::chrono::milliseconds{1500}, blink_off ); blink_off .time_limit( std::chrono::milliseconds{750}, blink_on ); }};
37
Реакция на сигнал включения и выключения для верхних состояний.
Достаточно просто перейти в другое состояние.
Агент blinking_led (пояснения)class blinking_led final : public so_5::agent_t { state_t off{ this }, blinking{ this }, blink_on{ initial_substate_of{ blinking } }, blink_off{ substate_of{ blinking } };
public : struct turn_on_off : public so_5::signal_t {};
blinking_led( context_t ctx ) : so_5::agent_t{ std::move(ctx) } { this >>= off;
off.just_switch_to< turn_on_off >( blinking ); blinking.just_switch_to< turn_on_off >( off );
blink_on .on_enter( []{ std::cout << "ON" << std::endl; } ) .on_exit( []{ std::cout << "off" << std::endl; } ) .time_limit( std::chrono::milliseconds{1500}, blink_off ); blink_off .time_limit( std::chrono::milliseconds{750}, blink_on ); }};
38
Реакция на вход и выход из состояния.
Агент blinking_led (пояснения)class blinking_led final : public so_5::agent_t { state_t off{ this }, blinking{ this }, blink_on{ initial_substate_of{ blinking } }, blink_off{ substate_of{ blinking } };
public : struct turn_on_off : public so_5::signal_t {};
blinking_led( context_t ctx ) : so_5::agent_t{ std::move(ctx) } { this >>= off;
off.just_switch_to< turn_on_off >( blinking ); blinking.just_switch_to< turn_on_off >( off );
blink_on .on_enter( []{ std::cout << "ON" << std::endl; } ) .on_exit( []{ std::cout << "off" << std::endl; } ) .time_limit( std::chrono::milliseconds{1500}, blink_off ); blink_off .time_limit( std::chrono::milliseconds{750}, blink_on ); }};
39
time_limit задает ограничение на время пребывания агента в состоянии.
По истечении этого времени агент автоматически меняет состояние.
Еще один пример: request_processor (код)class request_processor : public so_5::agent_t { state_t st_waiting{ this }, st_working{ this }, ...; const so_5::mbox_t src_;public : request_processor(context_t ctx, so_5::mbox_t src) : so_5::agent_t(std::move(ctx)), src_(std::move(src)) {}
virtual void so_define_agent() override { this >>= st_waiting; st_waiting .event(src_, &request_processor::on_request) .event(src_, &request_processor::on_status_when_waiting); st_working .event(src_, &request_processor::on_request) .event(src_, &request_processor::on_status_when_working); ... } ...private : void on_request(const request & cmd) {...} void on_status_when_waiting(mhood_t<get_status>) {...} void on_status_when_working(mhood_t<get_status>) {...}};
40
Еще один пример: request_processor (пояснения)class request_processor : public so_5::agent_t { state_t st_waiting{ this }, st_working{ this }, ...; const so_5::mbox_t src_;public : request_processor(context_t ctx, so_5::mbox_t src) : so_5::agent_t(std::move(ctx)), src_(std::move(src)) {}
virtual void so_define_agent() override { this >>= st_waiting; st_waiting .event(src_, &request_processor::on_request) .event(src_, &request_processor::on_status_when_waiting); st_working .event(src_, &request_processor::on_request) .event(src_, &request_processor::on_status_when_working); ... } ...private : void on_request(const request & cmd) {...} void on_status_when_waiting(mhood_t<get_status>) {...} void on_status_when_working(mhood_t<get_status>) {...}};
41
Почтовый ящик, из которого агент ожидает запросы. Создается кем-то и отдается агенту в качестве параметра конструктора.
Еще один пример: request_processor (пояснения)class request_processor : public so_5::agent_t { state_t st_waiting{ this }, st_working{ this }, ...; const so_5::mbox_t src_;public : request_processor(context_t ctx, so_5::mbox_t src) : so_5::agent_t(std::move(ctx)), src_(std::move(src)) {}
virtual void so_define_agent() override { this >>= st_waiting; st_waiting .event(src_, &request_processor::on_request) .event(src_, &request_processor::on_status_when_waiting); st_working .event(src_, &request_processor::on_request) .event(src_, &request_processor::on_status_when_working); ... } ...private : void on_request(const request & cmd) {...} void on_status_when_waiting(mhood_t<get_status>) {...} void on_status_when_working(mhood_t<get_status>) {...}};
42
Метод so_define_agent() дает возможность агенту произвести "настройку" перед тем, как начать работать внутри SObjectizer.
Обычно используется для создания подписок.
Еще один пример: request_processor (пояснения)class request_processor : public so_5::agent_t { state_t st_waiting{ this }, st_working{ this }, ...; const so_5::mbox_t src_;public : request_processor(context_t ctx, so_5::mbox_t src) : so_5::agent_t(std::move(ctx)), src_(std::move(src)) {}
virtual void so_define_agent() override { this >>= st_waiting; st_waiting .event(src_, &request_processor::on_request) .event(src_, &request_processor::on_status_when_waiting); st_working .event(src_, &request_processor::on_request) .event(src_, &request_processor::on_status_when_working); ... } ...private : void on_request(const request & cmd) {...} void on_status_when_waiting(mhood_t<get_status>) {...} void on_status_when_working(mhood_t<get_status>) {...}};
43
Подписка на сообщения.
Явным образом задается mbox из которого ожидаются сообщения.
Тип сообщения не указан явно, он выводится автоматически из типа аргумента обработчика.
Еще один пример: request_processor (пояснения)class request_processor : public so_5::agent_t { state_t st_waiting{ this }, st_working{ this }, ...; const so_5::mbox_t src_;public : request_processor(context_t ctx, so_5::mbox_t src) : so_5::agent_t(std::move(ctx)), src_(std::move(src)) {}
virtual void so_define_agent() override { this >>= st_waiting; st_waiting .event(src_, &request_processor::on_request) .event(src_, &request_processor::on_status_when_waiting); st_working .event(src_, &request_processor::on_request) .event(src_, &request_processor::on_status_when_working); ... } ...private : void on_request(const request & cmd) {...} void on_status_when_waiting(mhood_t<get_status>) {...} void on_status_when_working(mhood_t<get_status>) {...}};
44
Обработчик для сообщений с типом request.
Такой формат обработчика требует, чтобы request был сообщением, а не сигналом.
Еще один пример: request_processor (пояснения)class request_processor : public so_5::agent_t { state_t st_waiting{ this }, st_working{ this }, ...; const so_5::mbox_t src_;public : request_processor(context_t ctx, so_5::mbox_t src) : so_5::agent_t(std::move(ctx)), src_(std::move(src)) {}
virtual void so_define_agent() override { this >>= st_waiting; st_waiting .event(src_, &request_processor::on_request) .event(src_, &request_processor::on_status_when_waiting); st_working .event(src_, &request_processor::on_request) .event(src_, &request_processor::on_status_when_working); ... } ...private : void on_request(const request & cmd) {...} void on_status_when_waiting(mhood_t<get_status>) {...} void on_status_when_working(mhood_t<get_status>) {...}};
45
Обработчики для сообщений или сигналов с типом get_status.
Такой формат обработчика позволяет обрабатывать и сообщения, и сигналы. Что удобно при написании обобщенного кода в агентах.
Кооперации агентов
Кооперации агентов решают задачу одномоментной регистрации в SObjectizer группы взаимосвязанных агентов.
46
Наполнение и регистрация кооперацийso_5::environment_t & env = ...; // Доступ к SObjectizer.// Заставляем SObjectizer создать объект кооперации.auto coop = env.create_coop( "data_acquire_demo" );
// Наполняем кооперацию прикладными агентами. Заодно привязываем агентов к разным диспетчерам для того,// чтобы агенты работали на разных рабочих нитях.
coop->make_agent_with_binder<device_reader>( so_5::disp::one_thread::create_private_disp(env, "device")->binder(), ... );
coop->make_agent<data_processor>(...);
coop->make_agent_with_binder<db_writer>( so_5::disp::one_thread::create_private_disp(env, "db")->binder(), ... );
// Осталось только зарегистрировать кооперацию.env.register_coop(std::move(coop));
49
(1)
Наполнение и регистрация кооперацийso_5::environment_t & env = ...; // Доступ к SObjectizer.// Заставляем SObjectizer создать объект кооперации.auto coop = env.create_coop( "data_acquire_demo" );
// Наполняем кооперацию прикладными агентами. Заодно привязываем агентов к разным диспетчерам для того,// чтобы агенты работали на разных рабочих нитях.
coop->make_agent_with_binder<device_reader>( so_5::disp::one_thread::create_private_disp(env, "device")->binder(), ... );
coop->make_agent<data_processor>(...);
coop->make_agent_with_binder<db_writer>( so_5::disp::one_thread::create_private_disp(env, "db")->binder(), ... );
// Осталось только зарегистрировать кооперацию.env.register_coop(std::move(coop));
50
(2)
Наполнение и регистрация кооперацийso_5::environment_t & env = ...; // Доступ к SObjectizer.// Заставляем SObjectizer создать объект кооперации.auto coop = env.create_coop( "data_acquire_demo" );
// Наполняем кооперацию прикладными агентами. Заодно привязываем агентов к разным диспетчерам для того,// чтобы агенты работали на разных рабочих нитях.
coop->make_agent_with_binder<device_reader>( so_5::disp::one_thread::create_private_disp(env, "device")->binder(), ... );
coop->make_agent<data_processor>(...);
coop->make_agent_with_binder<db_writer>( so_5::disp::one_thread::create_private_disp(env, "db")->binder(), ... );
// Осталось только зарегистрировать кооперацию.env.register_coop(std::move(coop));
51
(3)
Регистрация кооперации – это просто?
1. Проверка уникальности имени.2. Проверка родительской кооперации (если есть).3. Запрос ресурсов у диспетчеров.4. Вызов so_define_agent() для каждого агента.5. Окончательная привязка агентов к диспетчерам, инициация
so_evt_start().
52
По традиции: hello_world#include <so_5/all.hpp>
class hello_world final : public so_5::agent_t {public : using so_5::agent_t::agent_t;
virtual void so_evt_start() override { std::cout << "Hello, World!" << std::endl; so_deregister_agent_coop_normally(); }};
int main() { so_5::launch([](so_5::environment_t & env) { env.register_agent_as_coop("hello", env.make_agent<hello_world>()); });
return 0;}
53
Распределенности в SObjectizer-5 нет
Распределенность "из коробки" была в SObjectizer-4. Но:
● для разных задач нужны разные протоколы (одно дело – передача телеметрии, другое – передача больших BLOB-ов);
● back pressure в асинхронном обмене сообщении сам по себе непрост. В случае IPC эта проблема усугубляется;
● интероперабельность с другими языками программирования. Точнее, ее отсутствие.
Поэтому в SObjectizer-5 распределенности нет.54
Реализация Модели Акторов не самоцель
У нас нет цели сделать из SObjectizer самую лучшую и/или самую полноценную реализацию Модели Акторов.
Мы говорим "акторный фреймворк" только потому, что:
● так проще объяснять, что это в принципе такое;● маркетинг.
56
Publish/Subscribe
Поддержка Publish/Subscribe была с самого начала.
Более того, в SObjectizer-5 понятие direct mbox-а появилось не сразу, изначально были только MPMC-mbox-ы.
57
CSP (Communicating Sequential Processes)
В декабре 2015-го добавлены message chains.
struct ping {}; struct pong {};
auto ch1 = so_5::create_mchain(env);auto ch2 = so_5::create_mchain(env);
std::thread pinger{ [ch1, ch2]{ while(true) { so_5::send<ping>(ch2); so_5::receive(ch1, so_5::infinite_wait, [](pong){}); }} };std::thread ponger{ [ch1, ch2]{ while(true) { so_5::receive(ch2, so_5::infinite_wait, [&ch1](ping) { so_5::send<pong>(ch1); } ); }} };
58
CSP (Communicating Sequential Processes)
В декабре 2015-го добавлены message chains.
struct ping {}; struct pong {};
auto ch1 = so_5::create_mchain(env);auto ch2 = so_5::create_mchain(env);
std::thread pinger{ [ch1, ch2]{ while(true) { so_5::send<ping>(ch2); so_5::receive(ch1, so_5::infinite_wait, [](pong){}); }} };std::thread ponger{ [ch1, ch2]{ while(true) { so_5::receive(ch2, so_5::infinite_wait, [&ch1](ping) { so_5::send<pong>(ch1); } ); }} };
59
Поэтому на самом-то деле...
It's all about in-process message dispatching!
60
Наша же цель в том, чтобы...
...предоставить разработчику небольшой качественный и стабильный инструмент.
Практичный и настраиваемый под нужды пользователя.
Стабильность и совместимость. За это мы готовы платить.
Например, поддержка Ubuntu 14.04 LTS и тамошнего gcc-4.8 для нас важнее, чем возможность использовать C++14 в коде SObjectizer.
61
В двух словах
Современный C++ для нас очень важен.
Даже так: если бы не C++11, SObjectizer-5 вряд ли появился бы.
Есть в C++11 вещи, без которых SObjectizer сейчас сложно представить...
63
Variadic templates и perfect forwarding
Используются в SObjectizer повсеместно:template<typename MSG, typename... ARGS>void send(const mbox_t & to, ARGS &&... args){ auto m = make_unique<MSG>(std::forward<ARGS>(args)...); to->deliver_message(std::move(m));}
class agent_coop_t {public : ... template< class AGENT, typename... ARGS > AGENT * make_agent( ARGS &&... args ) { auto a = make_unique< AGENT >( environment(), std::forward<ARGS>(args)... ); return this->add_agent( std::move( a ) ); }};
64
Лямбды (особенно в сочетании с шаблонами)
Ну очень сильно помогают. В том числе и для обеспечения гарантий безопасности исключений...void agent_core_t::next_coop_reg_step__update_registered_coop_map( const coop_ref_t & coop_ref, coop_t * parent_coop_ptr ){ m_registered_coop[ coop_ref->query_coop_name() ] = coop_ref; m_total_agent_count += coop_ref->query_agent_count();
so_5::details::do_with_rollback_on_exception( [&] { next_coop_reg_step__parent_child_relation( coop_ref, parent_coop_ptr ); }, [&] { m_total_agent_count -= coop_ref->query_agent_count(); m_registered_coop.erase( coop_ref->query_coop_name() ); } );}
65
(1)
(2)
(3)
auto и decltype
Сложно переоценить важность auto в современном C++. Особенно для вывода типа результата функции.template< typename MAIN_ACTION, typename ROLLBACK_ACTION >auto do_with_rollback_on_exception( MAIN_ACTION main_action, ROLLBACK_ACTION rollback_action ) -> decltype(main_action()) { using result_type = decltype(main_action());
using namespace rollback_on_exception_details;
rollbacker_t< ROLLBACK_ACTION > rollbacker{ rollback_action };
return executor< result_type, MAIN_ACTION, ROLLBACK_ACTION >::exec( main_action, rollbacker ); }
66
Стандартная библиотека C++11
Появление thread, mutex, condition_variable, atomic, unordered_map и пр. в стандартной библиотеке C++11 позволило нам избавиться от такой зависимости, как ACE.
Стало гораздо легче.
Правда, пришлось делать свою реализацию таймеров, но это уже совсем другая история...
67
Краткое резюме по современному C++
C++11/14 – это уже совсем другой C++.
Использовать современный C++ намного удобнее, особенно, если есть возможность пользоваться C++14.
Инфраструктура вокруг языка продолжает желать много лучшего.
Но мы над этим работаем ;)
68
SObjectizer: https://sourceforge.net/p/sobjectizer или https://github.com/eao197/so-5-5
Документация по SObjectizer: https://sourceforge.net/p/sobjectizer/wiki/Home/
Серия статей о SObjectizer на русском:SObjectizer: что это, для чего это и почему это выглядит именно так? От простого к сложному: Часть I, Часть II, Часть III. Акторы в виде конечных автоматов – это плохо или хорошо? Проблема перегрузки агентов и средства борьбы с ней. Нежная дружба агентов и исключений.
Серия презентаций о SObjectizer на английском "Dive into SObjectizer-5.5":Intro, Agent's States, More About Coops, Exceptions, Timers, Synchonous Interaction, Message Limits, Dispatchers, Message Chains.
70