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

70
C++ Russia 2017 Для чего мы делали свой акторный фреймворк и что из этого вышло? Евгений Охотников

Upload: yauheni-akhotnikau

Post on 12-Apr-2017

6.537 views

Category:

Software


1 download

TRANSCRIPT

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

C++ Russia 2017

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

этого вышло?

Евгений Охотников

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

А делали ли мы акторный фреймворк вообще?

?

2

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

Давным-давно, в далекой-далекой...

1994-й год.

Гомель.

КБ Системного Программирования.

Отдел АСУТП.

Попытка сделать объектно-ориентированную SCADA-систему.

SCADA - Supervisory Control And Data Acquisition

3

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

О SCADA-системах в двух словах

4

http://www.scadasoftware.net/

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

Объектная SCADA. Зачем?

Традиционный подход SCADA-систем в те времена:

● набор нумерованных или именованных каналов ввода-вывода (тегов);● слабая структуризация и почти отсутствие декомпозиции;● трудозатраты растут с увеличением числа тегов;● трудозатраты еще быстрее растут по мере усложнения логики

автоматизируемого техпроцесса.

Мы хотели устранить это за счет декомпозиции на объекты, обладающие состоянием и поведением.

5

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

Объектная SCADA. Что получилось?

Агенты:

● конечные автоматы с явно выделенными состояниями;● состояния агентов видны снаружи.

6

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

Объектная SCADA. Что получилось?

Взаимодействие через асинхронные сообщения:

● прицел на soft real-time;● распространение информации как в режиме 1:1, так и в

режиме 1:N.

7

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

Объектная SCADA. Что получилось?

Диспетчер.

Отвечал за обработку очередей сообщений с учетом приоритетов и требований soft real-time.

8

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

Объектная SCADA. Итог

SCADA Objectizer.

1998-й год.

Опробован в реальном проекте.

Прекратил свое существование в начале 2000-го :(

9

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

Предпосылки к перерождению

Начало 2002-го.

Компания "Интервэйл".

Двое участников разработки SCADA Objectizer.

Небольшая GUI-программа для управления подключенными к ПК устройствами...

10

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

Четвертое пришествие

Апрель 2002-го года.

SObjectizer = SCADA + Objectizer.

SObjectizer-4 (в четвертый раз все сначала).

11

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

Этапы большого пути

Май 2002-го: начало использования SO-4 в разработке.

Март 2006-го: перевод SO-4 в категорию OpenSource проектов под BSD-лицензией.

Сентябрь 2010-го: начало работ над SObjectizer-5.

Май 2013-го: публичный релиз SO-5 под BSD-лицензией.

Июль 2013-го: SObjectizer начал жить независимо от "Интервэйл".

12

Page 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

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

Тем не менее...

Для чего же мы делали свой фреймворк?

14

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

Как перестать бояться и...

...полюбить многопоточность?

Голые thread, mutex и condition_variable – это пот, кровь и боль.

Асинхронный обмен сообщениями рулит!И бибикает :)

Ибо shared nothing и вот этот вот все.

15

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

Бонусы асинхронного обмена сообщениями

● у каждого агента свое изолированное состояние (принцип shared nothing), упрощает жизнь в многопоточном коде;

● обмен сообщениями – естественный подход к решению некоторых типов задач;

● слабая связность между компонентами;● очень простая работа с таймерами (отложенные и

периодические сообщения);● низкий порог входа для новичков.

16

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

Давайте посмотрим на SObjectizer-5 с более близкого

расстояния

17

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

В SObjectizer есть сообщения

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

Сообщения это объекты. Тип сообщения наследуется от so_5::message_t.

18

Page 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

Page 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

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

Что такое Target для send?

В "традиционной" Модели Акторов адресатом сообщения является актор.

В SObjectizer сообщения отсылаются в "почтовый ящик".

Почтовый ящик в SObjectizer называется mbox.

21

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

Подписка на сообщение из mbox-а

Для получения сообщения из mbox-а нужно выполнить подписку. Только после этого сообщения будут доставляться агенту.

Подписку можно отменить. Сообщения доставляться перестанут.

Подписки агента автоматически удаляются, когда агент уничтожается.

Сообщения диспетчируются по типу, а не по содержимому.

22

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

Подписка на сообщение из mbox-а

23

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

Multi-Producer/Single-Consumer Mbox

Кто угодно может оправить. Подписаться может только один агент.MPSC-mbox создается для каждого агента автоматически. Использование MPSC-mbox-ов дает максимально близкое приближение к Модели Акторов.

24

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

Multi-Producer/Multi-Consumer Mbox

Кто угодно может оправить. Получат все подписчики.

MPMC-mbox нужно создавать вручную (можно с именем, можно без).Простейший вариант модели Publish/Subscribe.

25

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

В SObjectizer есть диспетчеры

Именно диспетчер определяет где и когда агент будет обрабатывать свои сообщения.

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

В приложении может быть запущено сразу несколько диспетчеров.

26

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

Примеры диспетчеров

one_thread: все агенты на одной и той же нити, все агенты используют одну очередь сообщений.

active_obj: у каждого агента своя собственная нить, у каждого агента собственная очередь сообщений.

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

27

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

Больше диспетчеров, хороших и разных

Всего в 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

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

Зачем так много?

29

Page 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

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

Агенты – это конечные автоматы

1. Мы не поддерживаем агентов в виде сопрограмм. Поэтому должен использоваться механизм callback-ов, чтобы отобразить N агентов на M рабочих нитей.

2. Ноги у SObjectizer растут из мира АСУТП, там конечные автоматы – это обычное дело.

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

31

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

Агент blinking_led (диаграмма состояний)

32

Page 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

Page 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

Определение двух верхнеуровневых состояний.

Page 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 является начальным подсостоянием.

Page 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);

Page 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

Реакция на сигнал включения и выключения для верхних состояний.

Достаточно просто перейти в другое состояние.

Page 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

Реакция на вход и выход из состояния.

Page 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 задает ограничение на время пребывания агента в состоянии.

По истечении этого времени агент автоматически меняет состояние.

Page 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

Page 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

Почтовый ящик, из которого агент ожидает запросы. Создается кем-то и отдается агенту в качестве параметра конструктора.

Page 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.

Обычно используется для создания подписок.

Page 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 из которого ожидаются сообщения.

Тип сообщения не указан явно, он выводится автоматически из типа аргумента обработчика.

Page 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 был сообщением, а не сигналом.

Page 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.

Такой формат обработчика позволяет обрабатывать и сообщения, и сигналы. Что удобно при написании обобщенного кода в агентах.

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

Кооперации агентов

Кооперации агентов решают задачу одномоментной регистрации в SObjectizer группы взаимосвязанных агентов.

46

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

Если бы были супервизоры, то...

47

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

Супервизоров нет, есть кооперации

48

Page 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)

Page 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)

Page 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)

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

Регистрация кооперации – это просто?

1. Проверка уникальности имени.2. Проверка родительской кооперации (если есть).3. Запрос ресурсов у диспетчеров.4. Вызов so_define_agent() для каждого агента.5. Окончательная привязка агентов к диспетчерам, инициация

so_evt_start().

52

Page 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

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

Распределенности в SObjectizer-5 нет

Распределенность "из коробки" была в SObjectizer-4. Но:

● для разных задач нужны разные протоколы (одно дело – передача телеметрии, другое – передача больших BLOB-ов);

● back pressure в асинхронном обмене сообщении сам по себе непрост. В случае IPC эта проблема усугубляется;

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

Поэтому в SObjectizer-5 распределенности нет.54

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

Куда же мы идем?

55

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

Реализация Модели Акторов не самоцель

У нас нет цели сделать из SObjectizer самую лучшую и/или самую полноценную реализацию Модели Акторов.

Мы говорим "акторный фреймворк" только потому, что:

● так проще объяснять, что это в принципе такое;● маркетинг.

56

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

Publish/Subscribe

Поддержка Publish/Subscribe была с самого начала.

Более того, в SObjectizer-5 понятие direct mbox-а появилось не сразу, изначально были только MPMC-mbox-ы.

57

Page 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

Page 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

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

Поэтому на самом-то деле...

It's all about in-process message dispatching!

60

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

Наша же цель в том, чтобы...

...предоставить разработчику небольшой качественный и стабильный инструмент.

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

Стабильность и совместимость. За это мы готовы платить.

Например, поддержка Ubuntu 14.04 LTS и тамошнего gcc-4.8 для нас важнее, чем возможность использовать C++14 в коде SObjectizer.

61

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

Кстати!

О современном C++

62

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

В двух словах

Современный C++ для нас очень важен.

Даже так: если бы не C++11, SObjectizer-5 вряд ли появился бы.

Есть в C++11 вещи, без которых SObjectizer сейчас сложно представить...

63

Page 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

Page 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)

Page 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

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

Стандартная библиотека C++11

Появление thread, mutex, condition_variable, atomic, unordered_map и пр. в стандартной библиотеке C++11 позволило нам избавиться от такой зависимости, как ACE.

Стало гораздо легче.

Правда, пришлось делать свою реализацию таймеров, но это уже совсем другая история...

67

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

Краткое резюме по современному C++

C++11/14 – это уже совсем другой C++.

Использовать современный C++ намного удобнее, особенно, если есть возможность пользоваться C++14.

Инфраструктура вокруг языка продолжает желать много лучшего.

Но мы над этим работаем ;)

68

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

Спасибо за терпение!

Вопросы?

69

Page 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