Successfully reported this slideshow.
We use your LinkedIn profile and activity data to personalize ads and to show you more relevant ads. You can change your ad preferences anytime.

Для чего мы делали свой акторный фреймворк и что из этого вышло?

8,911 views

Published on

Презентация доклада для конференции C++ Russia 2017.

Published in: Software
  • Be the first to comment

  • Be the first to like this

Для чего мы делали свой акторный фреймворк и что из этого вышло?

  1. 1. C++ Russia 2017 Для чего мы делали свой акторный фреймворк и что из этого вышло? Евгений Охотников
  2. 2. А делали ли мы акторный фреймворк вообще? ? 2
  3. 3. Давным-давно, в далекой-далекой... 1994-й год. Гомель. КБ Системного Программирования. Отдел АСУТП. Попытка сделать объектно-ориентированную SCADA-систему. SCADA - Supervisory Control And Data Acquisition 3
  4. 4. О SCADA-системах в двух словах 4 http://www.scadasoftware.net/
  5. 5. Объектная SCADA. Зачем? Традиционный подход SCADA-систем в те времена: ● набор нумерованных или именованных каналов ввода-вывода (тегов); ● слабая структуризация и почти отсутствие декомпозиции; ● трудозатраты растут с увеличением числа тегов; ● трудозатраты еще быстрее растут по мере усложнения логики автоматизируемого техпроцесса. Мы хотели устранить это за счет декомпозиции на объекты, обладающие состоянием и поведением. 5
  6. 6. Объектная SCADA. Что получилось? Агенты: ● конечные автоматы с явно выделенными состояниями; ● состояния агентов видны снаружи. 6
  7. 7. Объектная SCADA. Что получилось? Взаимодействие через асинхронные сообщения: ● прицел на soft real-time; ● распространение информации как в режиме 1:1, так и в режиме 1:N. 7
  8. 8. Объектная SCADA. Что получилось? Диспетчер. Отвечал за обработку очередей сообщений с учетом приоритетов и требований soft real-time. 8
  9. 9. Объектная SCADA. Итог SCADA Objectizer. 1998-й год. Опробован в реальном проекте. Прекратил свое существование в начале 2000-го :( 9
  10. 10. Предпосылки к перерождению Начало 2002-го. Компания "Интервэйл". Двое участников разработки SCADA Objectizer. Небольшая GUI-программа для управления подключенными к ПК устройствами... 10
  11. 11. Четвертое пришествие Апрель 2002-го года. SObjectizer = SCADA + Objectizer. SObjectizer-4 (в четвертый раз все сначала). 11
  12. 12. Этапы большого пути Май 2002-го: начало использования SO-4 в разработке. Март 2006-го: перевод SO-4 в категорию OpenSource проектов под BSD-лицензией. Сентябрь 2010-го: начало работ над SObjectizer-5. Май 2013-го: публичный релиз SO-5 под BSD-лицензией. Июль 2013-го: SObjectizer начал жить независимо от "Интервэйл". 12
  13. 13. На данный момент... Последняя стабильная версия 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
  14. 14. Тем не менее... Для чего же мы делали свой фреймворк? 14
  15. 15. Как перестать бояться и... ...полюбить многопоточность? Голые thread, mutex и condition_variable – это пот, кровь и боль. Асинхронный обмен сообщениями рулит! И бибикает :) Ибо shared nothing и вот этот вот все. 15
  16. 16. Бонусы асинхронного обмена сообщениями ● у каждого агента свое изолированное состояние (принцип shared nothing), упрощает жизнь в многопоточном коде; ● обмен сообщениями – естественный подход к решению некоторых типов задач; ● слабая связность между компонентами; ● очень простая работа с таймерами (отложенные и периодические сообщения); ● низкий порог входа для новичков. 16
  17. 17. Давайте посмотрим на SObjectizer-5 с более близкого расстояния 17
  18. 18. В SObjectizer есть сообщения Все взаимодействие между агентами в SObjectizer идет только через асинхронные сообщения. Сообщения это объекты. Тип сообщения наследуется от so_5::message_t. 18
  19. 19. Выглядят сообщения вот так: 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
  20. 20. Отсылаются сообщения вот так: // Безотлагательная отсылка сообщения. 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
  21. 21. Что такое Target для send? В "традиционной" Модели Акторов адресатом сообщения является актор. В SObjectizer сообщения отсылаются в "почтовый ящик". Почтовый ящик в SObjectizer называется mbox. 21
  22. 22. Подписка на сообщение из mbox-а Для получения сообщения из mbox-а нужно выполнить подписку. Только после этого сообщения будут доставляться агенту. Подписку можно отменить. Сообщения доставляться перестанут. Подписки агента автоматически удаляются, когда агент уничтожается. Сообщения диспетчируются по типу, а не по содержимому. 22
  23. 23. Подписка на сообщение из mbox-а 23
  24. 24. Multi-Producer/Single-Consumer Mbox Кто угодно может оправить. Подписаться может только один агент. MPSC-mbox создается для каждого агента автоматически. Использование MPSC-mbox-ов дает максимально близкое приближение к Модели Акторов. 24
  25. 25. Multi-Producer/Multi-Consumer Mbox Кто угодно может оправить. Получат все подписчики. MPMC-mbox нужно создавать вручную (можно с именем, можно без). Простейший вариант модели Publish/Subscribe. 25
  26. 26. В SObjectizer есть диспетчеры Именно диспетчер определяет где и когда агент будет обрабатывать свои сообщения. Каждый агент в SObjectizer должен быть привязан к конкретному диспетчеру. В приложении может быть запущено сразу несколько диспетчеров. 26
  27. 27. Примеры диспетчеров one_thread: все агенты на одной и той же нити, все агенты используют одну очередь сообщений. active_obj: у каждого агента своя собственная нить, у каждого агента собственная очередь сообщений. adv_thread_pool: агенты на группе нитей, агент может мигрировать с одной нити на другую, события одного агента могут быть запущены параллельно, если они отмечены как thread_safe. Очереди сообщений могут быть персональными или общими для нескольких агентов. 27
  28. 28. Больше диспетчеров, хороших и разных Всего в SObjectizer-5 "из коробки" доступно восемь типов диспетчеров: active_group active_obj adv_thread_pool one_thread prio_dedicated_threads::one_per_prio prio_one_thread::quoted_round_robin prio_one_thread::strictly_ordered thread_pool 28
  29. 29. Зачем так много? 29
  30. 30. В 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
  31. 31. Агенты – это конечные автоматы 1. Мы не поддерживаем агентов в виде сопрограмм. Поэтому должен использоваться механизм callback-ов, чтобы отобразить N агентов на M рабочих нитей. 2. Ноги у SObjectizer растут из мира АСУТП, там конечные автоматы – это обычное дело. 3. Когда есть ярко выраженные состояния и разное поведение в каждом из состояний конечный автомат удобнее, чем вызовы receive с последующим выбором обработчика. 31
  32. 32. Агент blinking_led (диаграмма состояний) 32
  33. 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 ); } }; 33
  34. 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 ); } }; 34 Определение двух верхнеуровневых состояний.
  35. 35. Агент 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 является начальным подсостоянием.
  36. 36. Агент 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);
  37. 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 ); } }; 37 Реакция на сигнал включения и выключения для верхних состояний. Достаточно просто перейти в другое состояние.
  38. 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 ); } }; 38 Реакция на вход и выход из состояния.
  39. 39. Агент 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 задает ограничение на время пребывания агента в состоянии. По истечении этого времени агент автоматически меняет состояние.
  40. 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>) {...} }; 40
  41. 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>) {...} }; 41 Почтовый ящик, из которого агент ожидает запросы. Создается кем-то и отдается агенту в качестве параметра конструктора.
  42. 42. Еще один пример: 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. Обычно используется для создания подписок.
  43. 43. Еще один пример: 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 из которого ожидаются сообщения. Тип сообщения не указан явно, он выводится автоматически из типа аргумента обработчика.
  44. 44. Еще один пример: 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 был сообщением, а не сигналом.
  45. 45. Еще один пример: 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. Такой формат обработчика позволяет обрабатывать и сообщения, и сигналы. Что удобно при написании обобщенного кода в агентах.
  46. 46. Кооперации агентов Кооперации агентов решают задачу одномоментной регистрации в SObjectizer группы взаимосвязанных агентов. 46
  47. 47. Если бы были супервизоры, то... 47
  48. 48. Супервизоров нет, есть кооперации 48
  49. 49. Наполнение и регистрация коопераций 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)
  50. 50. Наполнение и регистрация коопераций 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)
  51. 51. Наполнение и регистрация коопераций 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)
  52. 52. Регистрация кооперации – это просто? 1. Проверка уникальности имени. 2. Проверка родительской кооперации (если есть). 3. Запрос ресурсов у диспетчеров. 4. Вызов so_define_agent() для каждого агента. 5. Окончательная привязка агентов к диспетчерам, инициация so_evt_start(). 52
  53. 53. По традиции: 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
  54. 54. Распределенности в SObjectizer-5 нет Распределенность "из коробки" была в SObjectizer-4. Но: ● для разных задач нужны разные протоколы (одно дело – передача телеметрии, другое – передача больших BLOB-ов); ● back pressure в асинхронном обмене сообщении сам по себе непрост. В случае IPC эта проблема усугубляется; ● интероперабельность с другими языками программирования. Точнее, ее отсутствие. Поэтому в SObjectizer-5 распределенности нет. 54
  55. 55. Куда же мы идем? 55
  56. 56. Реализация Модели Акторов не самоцель У нас нет цели сделать из SObjectizer самую лучшую и/или самую полноценную реализацию Модели Акторов. Мы говорим "акторный фреймворк" только потому, что: ● так проще объяснять, что это в принципе такое; ● маркетинг. 56
  57. 57. Publish/Subscribe Поддержка Publish/Subscribe была с самого начала. Более того, в SObjectizer-5 понятие direct mbox-а появилось не сразу, изначально были только MPMC-mbox-ы. 57
  58. 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); } ); } } }; 58
  59. 59. 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
  60. 60. Поэтому на самом-то деле... It's all about in-process message dispatching! 60
  61. 61. Наша же цель в том, чтобы... ...предоставить разработчику небольшой качественный и стабильный инструмент. Практичный и настраиваемый под нужды пользователя. Стабильность и совместимость. За это мы готовы платить. Например, поддержка Ubuntu 14.04 LTS и тамошнего gcc-4.8 для нас важнее, чем возможность использовать C++14 в коде SObjectizer. 61
  62. 62. Кстати! О современном C++ 62
  63. 63. В двух словах Современный C++ для нас очень важен. Даже так: если бы не C++11, SObjectizer-5 вряд ли появился бы. Есть в C++11 вещи, без которых SObjectizer сейчас сложно представить... 63
  64. 64. 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
  65. 65. Лямбды (особенно в сочетании с шаблонами) Ну очень сильно помогают. В том числе и для обеспечения гарантий безопасности исключений... 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)
  66. 66. 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
  67. 67. Стандартная библиотека C++11 Появление thread, mutex, condition_variable, atomic, unordered_map и пр. в стандартной библиотеке C++11 позволило нам избавиться от такой зависимости, как ACE. Стало гораздо легче. Правда, пришлось делать свою реализацию таймеров, но это уже совсем другая история... 67
  68. 68. Краткое резюме по современному C++ C++11/14 – это уже совсем другой C++. Использовать современный C++ намного удобнее, особенно, если есть возможность пользоваться C++14. Инфраструктура вокруг языка продолжает желать много лучшего. Но мы над этим работаем ;) 68
  69. 69. Спасибо за терпение! Вопросы? 69
  70. 70. 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

×