SlideShare une entreprise Scribd logo
1  sur  153
Télécharger pour lire hors ligne
St.Petersburg C++ User Group
Акторы в C++:
взгляд старого практикующего
актородела
Евгений Охотников
Коротко о себе
Профессиональный разработчик с 1994-го.
● 1994-2000: АСУ ТП/SCADA и т.п.;
● 2001-2014: телеком, платежные сервисы;
● 2014-...: OpenSource-инструментарий для C++
"Тот самый eao197" (с) RSDN
Автор блога "Размышлизмы eao197" (http://eao197.blogspot.com/)
Сооснователь stiffstream.com
2
Начнем издалека
Модель Акторов
на пальцах
3
Многопоточность – это...
инструмент, который активно применяется в двух очень разных
областях:
● parallel computing;
● concurrent computing.
Concurrent computing может быть и без многопоточности.
Будем говорить о concurrent computing.
4
Многопоточность без ошибок?
Фантастика.
Даже имея 20 лет опыта.
Но почему?
5
Изменяемое разделяемое состояние
Главный фактор сложности в многопоточном программировании.
6
Убрать разделяемое состояние
Ничего не разделяем. Ни за что не боремся.
Пусть каждый поток владеет своими собственными данными. И
никто кроме потока-владельца не имеет к этим данным доступа.
Принцип shared nothing (широко известен в узких кругах).
https://en.wikipedia.org/wiki/Shared_nothing_architecture
7
Потокам нужно общаться, но как?
Кажется, что есть два способа:
1. Синхронно.
2. Асинхронно.
Но это только кажется...
Синхронное взаимодействие – это путь в никуда.
8
Асинхронный обмен сообщениями
9
X Y Z
Просто же!
Есть потоки. У каждого есть очередь входящих сообщений.
Поток спит, если очередь входящих сообщений пуста.
Поток просыпается, когда для него появляются входящие
сообщения.
Потоки общаются только через асинхронные сообщения.
10
Дополнительный бонус
Если сообщения несут копию данных, то мы получаем очень
приятный бонус:
прозрачный переход к распределенности.
11
Модель Акторов. Основные принципы
● актор – это некая сущность, обладающая поведением;
● акторы реагируют на входящие сообщения;
● получив сообщение актор может:
○ отослать некоторое (конечное) количество сообщений
другим акторам;
○ создать некоторое (конечное) количество новых акторов;
○ определить для себя новое поведение для обработки
последующих сообщений.
12
Актор – это некоторая сущность
Не более того.
Актором может быть отдельный процесс. Например, Erlang.
Актором может быть отдельный поток (OS thread, "green" thread,
fiber). Например, goroutine в Go может рассматриваться как актор.
Актором может быть объект, которому кто-то выделяет рабочий
контекст. Например, Akka.
13
Модель Акторов (официоз №1)
Появилась в 1973-ем году благодаря работам Карла Хьюитта (Carl
Hewitt).
Была затем развита в 1981-ом Уильямом Клингером (William
Clinger).
И в 1985-ом Гулом Агха (Gul Agha).
Это если говорить про формализацию Модели Акторов.
Неформально она открывалась и переоткрывалась
множество раз.
14
Модель Акторов (официоз №2)
Отправные точки для желающих погрузиться в формальную
теорию Модели Акторов:
https://en.wikipedia.org/wiki/History_of_the_Actor_model
https://en.wikipedia.org/wiki/Actor_model
https://en.wikipedia.org/wiki/Actor_model_theory
15
Вопрос ребром!
Почему Модель Акторов
привлекает внимание?
16
Чаще всего упоминается:
1. Простота. Нет разделяемых данных. Асинхронный обмен
сообщениями.
2. Аналогии с реальным миром. Многие вещи знакомы и понятны.
3. Масштабирование. Акторов может быть хоть миллион. Акторы могут
жить на разных нодах.
4. Отказоустойчивость. Какие-то акторы могут "падать", другие акторы
это обнаружат и исправят (см. систему супервизоров Erlang-а). Shared
nothing + message-passing сильно помогают и тут.
17
Модель Акторов как...
...способ смотреть на мир. Другими словами: если у вас в руках
молоток...
18
Вопрос на миллион
Модель Акторов ‒ это
серебряная пуля?
19
Границы применимости
Есть несколько маркеров, которые могут подсказать, что выбор
Модели Акторов будет оправданным.
Могут подсказать. А могут и не подсказать.
20
Маркер №1
Fire-and-Forget
21
Fire-and-Forget
1. Тотальный контроль не нужен.
2. Результаты начатых операций вот прямо здесь и сейчас не
нужны.
3. Если что-то не случилось, то ничего страшного.
Мы постоянно используем этот принцип в повседневной жизни
22
Fire-and-Forget: примеры "за" и "против"
Пример №1: две рабочие нити, одна только читает данные, вторая
только разбирает их.
Пример №2: коммит в БД.
23
Маркер №2
Конечные автоматы
24
Принципы Модели Акторов еще раз
● актор – это некая сущность, обладающая поведением;
● акторы реагируют на входящие сообщения;
● получив сообщение актор может:
○ отослать некоторое (конечное) количество сообщений
другим акторам;
○ создать некоторое (конечное) количество новых акторов;
○ определить для себя новое поведение для обработки
последующих сообщений.
25
О каких конечных автоматах речь?
26
Маркер №3
Архитектура Shared Nothing
27
Shared Nothing: контрольный в голову
Могут ли сущности в программе работать без каких либо
разделяемых данных?
Может ли каждая сущность быть представлена в виде отдельного
процесса?
Это таки экстремизм.
Но он отрезвляет.
28
Не маркер, но тем не менее
Таймеры
29
Напрямую не связано с Моделью Акторов, но...
...таймеры в виде отложенных сообщений ‒ это очень удобно на
практике.
Следствие того, что акторы общаются с внешним миром только
посредством сообщений.
Таймеры активно используются акторами из-за принципа
Fire-and-Forget.
30
Общие слова закончились...
...надеюсь, что пока было понятно
31
Ближе к телу!
Модель Акторов и C++:
миф или реальность?
32
Реальность!
● промышленная автоматизация (АСУ ТП);
● телеком;
● электронная и мобильная коммерция;
● имитационное моделирование;
● САПР;
● игростроение;
● ПО промежуточного слоя (СУБД, ...)
33
Есть несколько нюансов
Вызванных спецификой C++:
● небезопасностью;
● специфическими прикладными нишами (embedded,
high-performance, real-time и т.д.).
В C++ все акторные фреймворки очень разные и непохожие.
34
Есть готовые инструменты:
Наиболее известные:
● QP/C++ (двойная лицензия);
● Just::Thread Pro: Actors Edition (коммерческая лицензия);
● С++ Actor Framework (BSD-3-Clause лицензия);
● SObjectizer (BSD-3-Clause лицензия);
Кроме того:
● OOSMOS (двойная лицензия, язык C, но может использоваться и в C++);
● Asyncronous Agents Library (входит в Visual Studio).
Более-менее актуальный список в Wikipedia.
35
А теперь о главном
SObjectizer
36
Давным-давно, в далекой-далекой...
1994-й год.
Гомель. КБ Системного Программирования.
Отдел АСУТП.
Попытка сделать объектно-ориентированную SCADA-систему.
37
Объектная SCADA. Итог
SCADA Objectizer.
1998-й год.
Опробован в реальном проекте.
Прекратил свое существование в начале 2000-го :(
38
Предпосылки к перерождению
Начало 2002-го.
Компания "Интервэйл".
Двое участников разработки SCADA Objectizer.
Небольшая GUI-программа для управления подключенными к ПК
устройствами...
39
Апрель 2002-го года.
SObjectizer = SCADA + Objectizer.
SObjectizer-4 (в четвертый раз все сначала).
Четвертое пришествие
40
Май 2002-го: начало использования SO-4 в разработке.
Март 2006-го: перевод SO-4 в категорию OpenSource проектов под
BSD-лицензией.
Сентябрь 2010-го: начало работ над SObjectizer-5.
Май 2013-го: публичный релиз SO-5 под BSD-лицензией.
Июль 2013-го: SObjectizer начал жить независимо от "Интервэйл".
Этапы большого пути
41
В компании "Интервэйл":
● SMS/USSD-агрегатор;
● куски платежных сервисов, платформ электронной коммерции;
● система мониторинга для DevOps.
Вне "Интервэйла":
● имитационное моделирование;
● тестовые стенды;
● управление оборудованием;
● прототипирование.
Применение
42
SObjectizer v.5.5.22.1:
● май 2018;
● ~31 KLOC кода;
● ~35 KLOC тестов;
● ~10 KLOC примеров (работающих).
С++11. Минимальные требования: GCC-4.8, VC++ 12.0, clang-3.5
Платформы: Windows, Linux, FreeBSD, macOS, Android (CrystaX NDK).
SObjectizer-5: факты и цифры
43
...мы не делали реализацию Модели Акторов.
На самом деле...
44
...упростить для себя разработку многопоточного кода.
На момент начала работ над SObjectizer мы даже не знали про
Модель Акторов вообще.
Но получилась одна из возможных реализаций Модели Акторов.
Мы лишь хотели...
45
Хватит общих слов!
Что мы получили в SObjectizer-5?
46
Агенты. Кооперации агентов. (Агент == Актор и наоборот)
Сообщения. Подписки на сообщения.
Почтовые ящики.
Диспетчеры.
Из чего же, из чего же, из чего же...
47
Все взаимодействие между агентами в SObjectizer идет только
через асинхронные сообщения.
Сообщения это объекты.
Тип сообщения ‒ это ключ для поиска обработчиков сообщения.
В SObjectizer есть сообщения
48
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)
{}
};
Выглядят сообщения вот так:
49
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)
{}
};
Выглядят сообщения вот так:
50
Почему именно struct/class, а не tuple или что-то еще?
Структура гораздо проще эволюционирует.
Добавляем-удаляем-изменяем поле структуры и не нужно модифицировать
объявления обработчиков сообщения.
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)
{}
};
Выглядят сообщения вот так:
51
Почему именно struct/class, а не tuple или что-то еще?
Структура гораздо проще эволюционирует.
Добавляем-удаляем-изменяем поле структуры и не нужно модифицировать
объявления обработчиков сообщения.
struct reconfigure {
const std::string cfg_file_;
...
};
void my_agent::so_define_agent() {
so_subscribe_self().event(&my_agent::on_reconfigure);
...
}
void my_agent::on_reconfigure(mhood_t<reconfigure> cmd) {
...
}
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)
{}
};
Выглядят сообщения вот так:
52
Почему именно struct/class, а не tuple или что-то еще?
Структура гораздо проще эволюционирует.
Добавляем-удаляем-изменяем поле структуры и не нужно модифицировать
объявления обработчиков сообщения.
struct reconfigure {
const std::string cfg_file_;
...
};
void my_agent::so_define_agent() {
so_subscribe_self().event(&my_agent::on_reconfigure);
...
}
void my_agent::on_reconfigure(mhood_t<reconfigure> cmd) {
...
}
struct reconfigure {
enum class on_error_action { use_current, use_defaults, abort };
const std::string cfg_file_;
on_error_action on_error_;
...
};
void my_agent::so_define_agent() {
so_subscribe_self().event(&my_agent::on_reconfigure);
...
}
void my_agent::on_reconfigure(mhood_t<reconfigure> cmd) {
...
}
struct get_status : public so_5::signal_t {};
Еще есть сигналы
53
// Безотлагательная отсылка сообщения.
so_5::send<request>(target,
// Все это через perfect-forwarding идет в конструктор request-а.
make_id(), make_params(), make_payload(), calculate_deadline());
// Безотлагательная отсылка сигнала.
so_5::send<get_status>(target);
Отсылаются сообщения вот так:
54
// Безотлагательная отсылка сообщения.
so_5::send<request>(target,
// Все это через perfect-forwarding идет в конструктор request-а.
make_id(), make_params(), make_payload(), calculate_deadline());
// Безотлагательная отсылка сигнала.
so_5::send<get_status>(target);
Отсылаются сообщения вот так:
55
Эквивалентный код:
auto msg = std::make_unique<request>(
make_id(), make_params(), make_payload(), calculate_deadline());
target->deliver_message( std::move(msg) );
В "традиционной" Модели Акторов адресатом сообщения
является актор.
В SObjectizer сообщения отсылаются в "почтовый ящик".
Почтовый ящик в SObjectizer называется mbox.
Что такое Target для send?
56
Подписка на сообщение из mbox-а
57
MPMC (Multi-Producer/Multi-Consumer)
любой может послать,
любой может подписаться.
MPSC (Multi-Producer/Single-Consumer)
любой может послать,
только владелец может подписаться,
автоматически создается для каждого агента,
так же называется direct mbox.
Два типа mbox-ов
58
MPMC (Multi-Producer/Multi-Consumer)
любой может послать,
любой может подписаться.
MPSC (Multi-Producer/Single-Consumer)
любой может послать,
только владелец может подписаться,
автоматически создается для каждого агента,
так же называется direct mbox.
Два типа mbox-ов
59
Использование MPSC mbox-ов и дает
максимальное приближение к Модели Акторов.
MPMC (Multi-Producer/Multi-Consumer)
любой может послать,
любой может подписаться.
MPSC (Multi-Producer/Single-Consumer)
любой может послать,
только владелец может подписаться,
автоматически создается для каждого агента,
так же называется direct mbox.
Два типа mbox-ов
60
Почему именно mbox-ы и почему два типа
mbox-ов?
● ноги растут из мира АСУТП, там взаимодействие 1:N широко
используется;
● посредством mbox-ов получаем еще и модель Pub/Sub (mbox
выступает в качестве брокера, тип сообщения - в качестве имени
топика);
● легко делать разные трюки (so_5::extra::mboxes::round_robin,
so_5::extra::mboxes::collecting_mbox,
so_5::extra::mboxes::retained_msg).
Изначально в SO-5 был только один тип mbox-ов: MPMC.
// Подписка на сообщение из mbox-а.
so_subscribe(some_mbox).event(... /* обработчик */);
// Подписка на сообщение из собственного MPSC-mbox-а.
so_subscribe_self().event(... /* обработчик */);
Подписка на сообщения
61
// Подписка на сообщение из mbox-а.
so_subscribe(some_mbox).event(... /* обработчик */);
// Подписка на сообщение из собственного MPSC-mbox-а.
so_subscribe_self().event(... /* обработчик */);
Подписка на сообщения
62
Всего лишь более компактная запись для:
so_subscribe(so_direct_mbox()).event(... /* обработчик */);
Именно диспетчер определяет где и когда агент будет
обрабатывать свои сообщения.
Может быть запущено сразу несколько диспетчеров.
Поэтому каждый агент должен быть привязан к какому-то
диспетчеру.
В SObjectizer есть диспетчеры
63
В SObjectizer есть диспетчеры
64
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
Больше диспетчеров, хороших и разных
65
Зачем так много?
66
Зачем так много?
67
Иногда может потребоваться сделать диспетчер под задачу:
https://habr.com/post/353712/
Как правило, реализуются как объекты классов, унаследованных
от 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();
}
};
В SObjectizer есть агенты
68
Как правило, реализуются как объекты классов, унаследованных
от 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();
}
};
В SObjectizer есть агенты
69
Почему классы? Почему наследование?
Агенты в реальных приложениях гораздо сложнее оных из
HelloWorld-овских примеров.
Наглядный пример можно посмотреть здесь:
http://eao197.blogspot.com/2016/05/progc14-sobjectizer.html
ООП рулит и бибикает. И это не шутка.
Плюс явно выраженные состояния агентов...
Состояния позволяют реализовывать сложные иерархические
конечные автоматы:
● вложенные состояния;
● on_enter/on_exit для состояний;
● time_limit для состояний;
● deep/shallow history для состояний.
У агентов есть состояния
70
Состояния позволяют реализовывать сложные иерархические
конечные автоматы:
● вложенные состояния;
● on_enter/on_exit для состояний;
● time_limit для состояний;
● deep/shallow history для состояний.
У агентов есть состояния
71
Опять же все потому,
что корни растут из
АСУТП.
Пример агента с состояниями
72
class player_demo final : public so_5::agent_t {
state_t off{this, "off"},
on{this, "on"},
active{initial_substate_of{on}, "active"},
inactive{substate_of{on}, "inactive"};
...
Пример агента с состояниями
73
class player_demo final : public so_5::agent_t {
state_t off{this, "off"},
on{this, "on"},
active{initial_substate_of{on}, "active"},
inactive{substate_of{on}, "inactive"};
...
Пример агента с состояниями
74
Состояния самого верхнего уровня.
Не имеют родительских состояний.
class player_demo final : public so_5::agent_t {
state_t off{this, "off"},
on{this, "on"},
active{initial_substate_of{on}, "active"},
inactive{substate_of{on}, "inactive"};
...
Пример агента с состояниями
75
Подсостояния состояния on.
class player_demo final : public so_5::agent_t {
state_t off{this, "off"},
on{this, "on"},
active{initial_substate_of{on}, "active"},
inactive{substate_of{on}, "inactive"};
...
Пример агента с состояниями
76
Начальное подсостояние для состояния on.
Как только агент переходит в состояние on,
автоматически активируется подсостояние
on::active.
void so_define_agent() override {
this >>= off;
off.event([&](mhood_t<turn_on_off>){... /* включить устройство */});
on.event([&](mhood_t<turn_on_off>{... /* выключить устройство */});
active
.on_enter([&]{... /* зажечь экран */})
.on_exit([&]{... /* погасить экран */})
.time_limit(15s, inactive)
.event([&](mhood_t<volume_up> cmd){... /* реакция на volume_up */})
.event([&](mhood_t<volume_down> cmd){... /* реакция на volume_down */});
inactive
.transfer_to_state<volume_up>(active)
.transfer_to_state<volume_down>(active);
}
Пример агента с состояниями
77
void so_define_agent() override {
this >>= off;
off.event([&](mhood_t<turn_on_off>){... /* включить устройство */});
on.event([&](mhood_t<turn_on_off>{... /* выключить устройство */});
active
.on_enter([&]{... /* зажечь экран */})
.on_exit([&]{... /* погасить экран */})
.time_limit(15s, inactive)
.event([&](mhood_t<volume_up> cmd){... /* реакция на volume_up */})
.event([&](mhood_t<volume_down> cmd){... /* реакция на volume_down */});
inactive
.transfer_to_state<volume_up>(active)
.transfer_to_state<volume_down>(active);
}
Пример агента с состояниями
78
Агент должен начать работать в состояние off.
По умолчанию агент начинает работать в специальном
default_state.
Поэтому нужно явно перевести агента в состояние off.
void so_define_agent() override {
this >>= off;
off.event([&](mhood_t<turn_on_off>){... /* включить устройство */});
on.event([&](mhood_t<turn_on_off>{... /* выключить устройство */});
active
.on_enter([&]{... /* зажечь экран */})
.on_exit([&]{... /* погасить экран */})
.time_limit(15s, inactive)
.event([&](mhood_t<volume_up> cmd){... /* реакция на volume_up */})
.event([&](mhood_t<volume_down> cmd){... /* реакция на volume_down */});
inactive
.transfer_to_state<volume_up>(active)
.transfer_to_state<volume_down>(active);
}
Пример агента с состояниями
79
Агент должен начать работать в состояние off.
По умолчанию агент начинает работать в специальном
default_state.
Поэтому нужно явно перевести агента в состояние on.
Специально для тех, кто не любит подобный
синтаксис.
Есть альтернативные варианты записи:
so_change_state(off);
off.activate();
void so_define_agent() override {
this >>= off;
off.event([&](mhood_t<turn_on_off>){... /* включить устройство */});
on.event([&](mhood_t<turn_on_off>{... /* выключить устройство */});
active
.on_enter([&]{... /* зажечь экран */})
.on_exit([&]{... /* погасить экран */})
.time_limit(15s, inactive)
.event([&](mhood_t<volume_up> cmd){... /* реакция на volume_up */})
.event([&](mhood_t<volume_down> cmd){... /* реакция на volume_down */});
inactive
.transfer_to_state<volume_up>(active)
.transfer_to_state<volume_down>(active);
}
Пример агента с состояниями
80
Подписка на событие в определенном состоянии.
void so_define_agent() override {
this >>= off;
off.event([&](mhood_t<turn_on_off>){... /* включить устройство */});
on.event([&](mhood_t<turn_on_off>{... /* выключить устройство */});
active
.on_enter([&]{... /* зажечь экран */})
.on_exit([&]{... /* погасить экран */})
.time_limit(15s, inactive)
.event([&](mhood_t<volume_up> cmd){... /* реакция на volume_up */})
.event([&](mhood_t<volume_down> cmd){... /* реакция на volume_down */});
inactive
.transfer_to_state<volume_up>(active)
.transfer_to_state<volume_down>(active);
}
Пример агента с состояниями
81
Подписка на событие в определенном состоянии.
Можно записать и по-другому:
so_subscribe_self().in(off).event(...);
void so_define_agent() override {
this >>= off;
off.event([&](mhood_t<turn_on_off>){... /* включить устройство */});
on.event([&](mhood_t<turn_on_off>{... /* выключить устройство */});
active
.on_enter([&]{... /* зажечь экран */})
.on_exit([&]{... /* погасить экран */})
.time_limit(15s, inactive)
.event([&](mhood_t<volume_up> cmd){... /* реакция на volume_up */})
.event([&](mhood_t<volume_down> cmd){... /* реакция на volume_down */});
inactive
.transfer_to_state<volume_up>(active)
.transfer_to_state<volume_down>(active);
}
Пример агента с состояниями
82
Определение реакции на вход в состояние и на
выход из состояния.
void so_define_agent() override {
this >>= off;
off.event([&](mhood_t<turn_on_off>){... /* включить устройство */});
on.event([&](mhood_t<turn_on_off>{... /* выключить устройство */});
active
.on_enter([&]{... /* зажечь экран */})
.on_exit([&]{... /* погасить экран */})
.time_limit(15s, inactive)
.event([&](mhood_t<volume_up> cmd){... /* реакция на volume_up */})
.event([&](mhood_t<volume_down> cmd){... /* реакция на volume_down */});
inactive
.transfer_to_state<volume_up>(active)
.transfer_to_state<volume_down>(active);
}
Пример агента с состояниями
83
Ограничиваем время пребывания в состоянии active.
Через 15s после входа в active автоматически переводим
агента в состояние inactive.
void so_define_agent() override {
this >>= off;
off.event([&](mhood_t<turn_on_off>){... /* включить устройство */});
on.event([&](mhood_t<turn_on_off>{... /* выключить устройство */});
active
.on_enter([&]{... /* зажечь экран */})
.on_exit([&]{... /* погасить экран */})
.time_limit(15s, inactive)
.event([&](mhood_t<volume_up> cmd){... /* реакция на volume_up */})
.event([&](mhood_t<volume_down> cmd){... /* реакция на volume_down */});
inactive
.transfer_to_state<volume_up>(active)
.transfer_to_state<volume_down>(active);
}
Пример агента с состояниями
84
Делегируем обработку сообщений volume_up и
volume_down состоянию active.
Агент переводится в состояние active и обработчики
для этих сообщений ищутся среди подписок состояния
active.
off.event([&](mhood_t<turn_on_off>) {
... /* специфический для инициализации устройства код */
this >>= on;
});
on.event([&](mhood_t<turn_on_off>) {
... /* специфический для деинициализации устройства код */
this >>= off;
});
Пример агента с состояниями
85
off.event([&](mhood_t<turn_on_off>) {
... /* специфический для инициализации устройства код */
this >>= on;
});
on.event([&](mhood_t<turn_on_off>) {
... /* специфический для деинициализации устройства код */
this >>= off;
});
Пример агента с состояниями
86
Активными станут сразу два состояния: on и on::active.
У состояния on::active будет вызван обработчик on_enter.
off.event([&](mhood_t<turn_on_off>) {
... /* специфический для инициализации устройства код */
this >>= on;
});
on.event([&](mhood_t<turn_on_off>) {
... /* специфический для деинициализации устройства код */
this >>= off;
});
Пример агента с состояниями
87
Кооперации агентов решают задачу одномоментной регистрации в
SObjectizer группы взаимосвязанных агентов.
Кооперации агентов
88
Кооперации агентов
89
Наполнение и регистрация коопераций
90
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));
(1)
Наполнение и регистрация коопераций
91
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));
(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));
Наполнение и регистрация коопераций
92
Наполнение и регистрация коопераций
93
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)); (3)
Наполнение и регистрация коопераций
94
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)); (3)
Альтернативный вариант регистрации кооперации:
so_5::environment_t & env = ...; // Доступ к SObjectizer.
// Заставляем SObjectizer создать объект кооперации,
// а затем и зарегистрировать его.
env.introduce_coop("data_acquire_demo", [&](so_5::coop_t & coop) {
// Наполняем кооперацию прикладными агентами. Заодно привязываем агентов к
// разным диспетчерам для того, чтобы агенты работали на разных рабочих
// нитях.
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(), ... );
}); // Здесь кооперация будет зарегистрирована автоматически.
Шаги №1 и №3 выполняет сам SObjectizer.
Два агента: pinger и ponger.
Агент pinger отсылает сигналы ping агенту ponger.
Агент ponger в ответ отсылает сигналы pong.
Работают некоторое время.
В конце агенты сообщают сколько сообщений получили.
Классика жанра: ping-pong
95
Первая на базе MPMC (multi-consumer) mbox-ов.
Вторая на базе MPSC (single-consumer или direct) mbox-ов.
Посмотрим две реализации
96
Первая на базе MPMC (multi-consumer) mbox-ов.
Вторая на базе MPSC (single-consumer или direct) mbox-ов.
Посмотрим две реализации
97
Достаточно одного MPMC-mbox-а.
Агент pinger подписывается на pong.
Агент ponger подписывается на ping.
Разные подписки.
Поэтому MPMC точно знает кому и что доставлять.
Практически как в случае одного MQ-брокера и двух топиков с
разными именами.
#include <so_5/all.hpp>
struct ping final : public so_5::signal_t {};
struct pong final : public so_5::signal_t {};
Ping-pong через MPMC-mbox (1)
98
class pinger final : public so_5::agent_t {
public:
pinger(context_t ctx, so_5::mbox_t mbox)
: so_5::agent_t{std::move(ctx)}, mbox_{std::move(mbox)} {}
void so_define_agent() override;
void so_evt_start() override;
void so_evt_finish() override;
private:
const so_5::mbox_t mbox_;
unsigned long long pongs_{};
void on_pong(mhood_t<pong>);
};
Ping-pong через MPMC-mbox (2)
99
class pinger final : public so_5::agent_t {
public:
pinger(context_t ctx, so_5::mbox_t mbox)
: so_5::agent_t{std::move(ctx)}, mbox_{std::move(mbox)} {}
void so_define_agent() override;
void so_evt_start() override;
void so_evt_finish() override;
private:
const so_5::mbox_t mbox_;
unsigned long long pongs_{};
void on_pong(mhood_t<pong>);
};
Ping-pong через MPMC-mbox (2)
100
class pinger final : public so_5::agent_t {
public:
pinger(context_t ctx, so_5::mbox_t mbox)
: so_5::agent_t{std::move(ctx)}, mbox_{std::move(mbox)} {}
void so_define_agent() override;
void so_evt_start() override;
void so_evt_finish() override;
private:
const so_5::mbox_t mbox_;
unsigned long long pongs_{};
void on_pong(mhood_t<pong>);
};
Ping-pong через MPMC-mbox (2)
101
Вызывается SObjectizer-ом во
время регистрации кооперации для
настройки агента.
class pinger final : public so_5::agent_t {
public:
pinger(context_t ctx, so_5::mbox_t mbox)
: so_5::agent_t{std::move(ctx)}, mbox_{std::move(mbox)} {}
void so_define_agent() override;
void so_evt_start() override;
void so_evt_finish() override;
private:
const so_5::mbox_t mbox_;
unsigned long long pongs_{};
void on_pong(mhood_t<pong>);
};
Ping-pong через MPMC-mbox (2)
102
Первый метод, который вызывается
у агента на его рабочем контексте
после успешной регистрации
кооперации.
class pinger final : public so_5::agent_t {
public:
pinger(context_t ctx, so_5::mbox_t mbox)
: so_5::agent_t{std::move(ctx)}, mbox_{std::move(mbox)} {}
void so_define_agent() override;
void so_evt_start() override;
void so_evt_finish() override;
private:
const so_5::mbox_t mbox_;
unsigned long long pongs_{};
void on_pong(mhood_t<pong>);
};
Ping-pong через MPMC-mbox (2)
103
Последний метод, который
вызывается у агента на его рабочем
контексте перед тем как кооперация
будет полностью дерегистрирована.
class pinger final : public so_5::agent_t {
public:
pinger(context_t ctx, so_5::mbox_t mbox)
: so_5::agent_t{std::move(ctx)}, mbox_{std::move(mbox)} {}
void so_define_agent() override;
void so_evt_start() override;
void so_evt_finish() override;
private:
const so_5::mbox_t mbox_;
unsigned long long pongs_{};
void on_pong(mhood_t<pong>);
};
Ping-pong через MPMC-mbox (2)
104
Обработчик нужного нам сигнала.
void so_define_agent() override {
so_subscribe(mbox_).event(&pinger::on_pong);
}
void so_evt_start() override {
so_5::send<ping>(mbox_);
}
void so_evt_finish() override {
std::cout << "pongs received: " + std::to_string(pongs_) << std::endl;
}
void on_pong(mhood_t<pong>) {
++pongs_;
so_5::send<ping>(mbox_);
}
Ping-pong через MPMC-mbox (3)
105
void so_define_agent() override {
so_subscribe(mbox_).event(&pinger::on_pong);
}
void so_evt_start() override {
so_5::send<ping>(mbox_);
}
void so_evt_finish() override {
std::cout << "pongs received: " + std::to_string(pongs_) << std::endl;
}
void on_pong(mhood_t<pong>) {
++pongs_;
so_5::send<ping>(mbox_);
}
Ping-pong через MPMC-mbox (3)
106
Подписка на интересующий нас сигнал
из заданного mbox-а.
void so_define_agent() override {
so_subscribe(mbox_).event(&pinger::on_pong);
}
void so_evt_start() override {
so_5::send<ping>(mbox_);
}
void so_evt_finish() override {
std::cout << "pongs received: " + std::to_string(pongs_) << std::endl;
}
void on_pong(mhood_t<pong>) {
++pongs_;
so_5::send<ping>(mbox_);
}
Ping-pong через MPMC-mbox (3)
107
Сразу отсылаем первый ping как только
начали работать.
void so_define_agent() override {
so_subscribe(mbox_).event(&pinger::on_pong);
}
void so_evt_start() override {
so_5::send<ping>(mbox_);
}
void so_evt_finish() override {
std::cout << "pongs received: " + std::to_string(pongs_) << std::endl;
}
void on_pong(mhood_t<pong>) {
++pongs_;
so_5::send<ping>(mbox_);
}
Ping-pong через MPMC-mbox (3)
108
В самом конце печатаем результаты.
void so_define_agent() override {
so_subscribe(mbox_).event(&pinger::on_pong);
}
void so_evt_start() override {
so_5::send<ping>(mbox_);
}
void so_evt_finish() override {
std::cout << "pongs received: " + std::to_string(pongs_) << std::endl;
}
void on_pong(mhood_t<pong>) {
++pongs_;
so_5::send<ping>(mbox_);
}
Ping-pong через MPMC-mbox (3)
109
Реакция на ответный pong:
- обновляем статистику;
- отсылаем очередной ping.
class ponger final : public so_5::agent_t {
public:
ponger(context_t ctx, so_5::mbox_t mbox)
: so_5::agent_t{std::move(ctx)}, mbox_{std::move(mbox)} {
}
void so_define_agent() override;
void so_evt_finish() override;
private:
const so_5::mbox_t mbox_;
unsigned long long pings_{};
void on_ping(mhood_t<ping>);
};
Ping-pong через MPMC-mbox (4)
110
class ponger final : public so_5::agent_t {
public:
ponger(context_t ctx, so_5::mbox_t mbox)
: so_5::agent_t{std::move(ctx)}, mbox_{std::move(mbox)} {
}
void so_define_agent() override;
void so_evt_finish() override;
private:
const so_5::mbox_t mbox_;
unsigned long long pings_{};
void on_ping(mhood_t<ping>);
};
Ping-pong через MPMC-mbox (4)
111
В отличии от pinger-а, у ponger-а нет метода
so_evt_start. Это потому, что ponger-у ничего не
нужно делать самому, он будет отвечать на
сигналы от pinger-а.
void so_define_agent() override {
so_subscribe(mbox_).event(&ponger::on_ping);
}
void so_evt_finish() override {
std::cout << "pings received: " + std::to_string(pings_) << std::endl;
}
void on_ping(mhood_t<ping>) {
++pings_;
so_5::send<pong>(mbox_);
}
Ping-pong через MPMC-mbox (5)
112
int main() {
so_5::launch([](so_5::environment_t & env) {
env.introduce_coop("pinger-ponger",
so_5::disp::active_obj::create_private_disp(env)->binder(),
[&](so_5::coop_t & coop) {
const auto mbox = env.create_mbox();
coop.make_agent<pinger>(mbox);
coop.make_agent<ponger>(mbox);
} );
std::this_thread::sleep_for(std::chrono::seconds{1});
env.stop();
});
}
Ping-pong через MPMC-mbox (6)
113
int main() {
so_5::launch([](so_5::environment_t & env) {
env.introduce_coop("pinger-ponger",
so_5::disp::active_obj::create_private_disp(env)->binder(),
[&](so_5::coop_t & coop) {
const auto mbox = env.create_mbox();
coop.make_agent<pinger>(mbox);
coop.make_agent<ponger>(mbox);
} );
std::this_thread::sleep_for(std::chrono::seconds{1});
env.stop();
});
}
Ping-pong через MPMC-mbox (6)
114
Запускает SObjectizer.
Возвращает управление после того, как SObjectizer будет остановлен.
int main() {
so_5::launch([](so_5::environment_t & env) {
env.introduce_coop("pinger-ponger",
so_5::disp::active_obj::create_private_disp(env)->binder(),
[&](so_5::coop_t & coop) {
const auto mbox = env.create_mbox();
coop.make_agent<pinger>(mbox);
coop.make_agent<ponger>(mbox);
} );
std::this_thread::sleep_for(std::chrono::seconds{1});
env.stop();
});
}
Ping-pong через MPMC-mbox (6)
115
Действия, которые должны быть
выполнены сразу после запуска
SObjectizer.
int main() {
so_5::launch([](so_5::environment_t & env) {
env.introduce_coop("pinger-ponger",
so_5::disp::active_obj::create_private_disp(env)->binder(),
[&](so_5::coop_t & coop) {
const auto mbox = env.create_mbox();
coop.make_agent<pinger>(mbox);
coop.make_agent<ponger>(mbox);
} );
std::this_thread::sleep_for(std::chrono::seconds{1});
env.stop();
});
}
Ping-pong через MPMC-mbox (6)
116
Диспетчер active_obj выделит
отдельную рабочую нить каждому
агенту кооперации.
int main() {
so_5::launch([](so_5::environment_t & env) {
env.introduce_coop("pinger-ponger",
so_5::disp::active_obj::create_private_disp(env)->binder(),
[&](so_5::coop_t & coop) {
const auto mbox = env.create_mbox();
coop.make_agent<pinger>(mbox);
coop.make_agent<ponger>(mbox);
} );
std::this_thread::sleep_for(std::chrono::seconds{1});
env.stop();
});
}
Ping-pong через MPMC-mbox (6)
117
Создаем mbox для обмена
сообщениями между агентами.
int main() {
so_5::launch([](so_5::environment_t & env) {
env.introduce_coop("pinger-ponger",
so_5::disp::active_obj::create_private_disp(env)->binder(),
[&](so_5::coop_t & coop) {
const auto mbox = env.create_mbox();
coop.make_agent<pinger>(mbox);
coop.make_agent<ponger>(mbox);
} );
std::this_thread::sleep_for(std::chrono::seconds{1});
env.stop();
});
}
Ping-pong через MPMC-mbox (6)
118
Наполняем кооперацию агентами.
int main() {
so_5::launch([](so_5::environment_t & env) {
env.introduce_coop("pinger-ponger",
so_5::disp::active_obj::create_private_disp(env)->binder(),
[&](so_5::coop_t & coop) {
const auto mbox = env.create_mbox();
coop.make_agent<pinger>(mbox);
coop.make_agent<ponger>(mbox);
} );
std::this_thread::sleep_for(std::chrono::seconds{1});
env.stop();
});
}
Ping-pong через MPMC-mbox (6)
119
Даем одну секунду на работу и
останавливаем SObjectizer.
pongs received: 463979
pings received: 463980
Ping-pong через MPMC-mbox. Результат
120
Ubuntu 16.04 LTS, x86_64
Intel(R) Core(TM) i7-3667U CPU @ 2.00GHz
GCC 5.5.0
class pinger final : public so_5::agent_t {
public:
using so_5::agent_t::agent_t;
void set_ponger_mbox(so_5::mbox_t mbox) { ponger_ = std::move(mbox); }
void so_define_agent() override;
void so_evt_start() override;
void so_evt_finish() override;
private:
so_5::mbox_t ponger_;
unsigned long long pongs_{};
void on_pong(mhood_t<pong>);
};
Ping-pong через MPSC-mbox (1)
121
class pinger final : public so_5::agent_t {
public:
using so_5::agent_t::agent_t;
void set_ponger_mbox(so_5::mbox_t mbox) { ponger_ = std::move(mbox); }
void so_define_agent() override;
void so_evt_start() override;
void so_evt_finish() override;
private:
so_5::mbox_t ponger_;
unsigned long long pongs_{};
void on_pong(mhood_t<pong>);
};
Ping-pong через MPSC-mbox (1)
122
Собственный конструктор уже не нужен.
Наследуем конструкторы из базового класса.
class pinger final : public so_5::agent_t {
public:
using so_5::agent_t::agent_t;
void set_ponger_mbox(so_5::mbox_t mbox) { ponger_ = std::move(mbox); }
void so_define_agent() override;
void so_evt_start() override;
void so_evt_finish() override;
private:
so_5::mbox_t ponger_;
unsigned long long pongs_{};
void on_pong(mhood_t<pong>);
};
Ping-pong через MPSC-mbox (1)
123
void pinger::so_define_agent() {
so_subscribe_self().event(&pinger::on_pong);
}
void pinger::so_evt_start() {
so_5::send<ping>(ponger_);
}
void pinger::so_evt_finish() {
std::cout << "pongs received: " + std::to_string(pongs_) << std::endl;
}
void pinger::on_pong(mhood_t<pong>) {
++pongs_;
so_5::send<ping>(ponger_);
}
Ping-pong через MPSC-mbox (2)
124
void pinger::so_define_agent() {
so_subscribe_self().event(&pinger::on_pong);
}
void pinger::so_evt_start() {
so_5::send<ping>(ponger_);
}
void pinger::so_evt_finish() {
std::cout << "pongs received: " + std::to_string(pongs_) << std::endl;
}
void pinger::on_pong(mhood_t<pong>) {
++pongs_;
so_5::send<ping>(ponger_);
}
Ping-pong через MPSC-mbox (2)
125
Теперь мы ждем сигналы pong из персонального
direct mbox-а.
class ponger final : public so_5::agent_t {
public:
using so_5::agent_t::agent_t;
void set_pinger_mbox(so_5::mbox_t mbox) { pinger_ = std::move(mbox); }
void so_define_agent() override;
void so_evt_finish() override;
private:
so_5::mbox_t pinger_;
unsigned long long pings_{};
void on_ping(mhood_t<ping>);
};
Ping-pong через MPSC-mbox (3)
126
class ponger final : public so_5::agent_t {
public:
using so_5::agent_t::agent_t;
void set_pinger_mbox(so_5::mbox_t mbox) { pinger_ = std::move(mbox); }
void so_define_agent() override;
void so_evt_finish() override;
private:
so_5::mbox_t pinger_;
unsigned long long pings_{};
void on_ping(mhood_t<ping>);
};
Ping-pong через MPSC-mbox (3)
127
void ponger::so_define_agent() {
so_subscribe_self().event(&ponger::on_ping);
}
void ponger::so_evt_finish() {
std::cout << "pings received: " + std::to_string(pings_) << std::endl;
}
void ponger::on_ping(mhood_t<ping>) {
++pings_;
so_5::send<pong>(pinger_);
}
Ping-pong через MPSC-mbox (4)
128
void ponger::so_define_agent() {
so_subscribe_self().event(&ponger::on_ping);
}
void ponger::so_evt_finish() {
std::cout << "pings received: " + std::to_string(pings_) << std::endl;
}
void ponger::on_ping(mhood_t<ping>) {
++pings_;
so_5::send<pong>(pinger_);
}
Ping-pong через MPSC-mbox (4)
129
int main() {
so_5::launch([](so_5::environment_t & env) {
env.introduce_coop("pinger-ponger",
so_5::disp::active_obj::create_private_disp(env)->binder(),
[](so_5::coop_t & coop) {
auto a_pinger = coop.make_agent<pinger>();
auto a_ponger = coop.make_agent<ponger>();
a_pinger->set_ponger_mbox(a_ponger->so_direct_mbox());
a_ponger->set_pinger_mbox(a_pinger->so_direct_mbox());
} );
std::this_thread::sleep_for(std::chrono::seconds{1});
env.stop();
});
}
Ping-pong через MPSC-mbox (5)
130
int main() {
so_5::launch([](so_5::environment_t & env) {
env.introduce_coop("pinger-ponger",
so_5::disp::active_obj::create_private_disp(env)->binder(),
[](so_5::coop_t & coop) {
auto a_pinger = coop.make_agent<pinger>();
auto a_ponger = coop.make_agent<ponger>();
a_pinger->set_ponger_mbox(a_ponger->so_direct_mbox());
a_ponger->set_pinger_mbox(a_pinger->so_direct_mbox());
} );
std::this_thread::sleep_for(std::chrono::seconds{1});
env.stop();
});
}
Ping-pong через MPSC-mbox (5)
131
Сперва нужно создать агентов...
int main() {
so_5::launch([](so_5::environment_t & env) {
env.introduce_coop("pinger-ponger",
so_5::disp::active_obj::create_private_disp(env)->binder(),
[](so_5::coop_t & coop) {
auto a_pinger = coop.make_agent<pinger>();
auto a_ponger = coop.make_agent<ponger>();
a_pinger->set_ponger_mbox(a_ponger->so_direct_mbox());
a_ponger->set_pinger_mbox(a_pinger->so_direct_mbox());
} );
std::this_thread::sleep_for(std::chrono::seconds{1});
env.stop();
});
}
Ping-pong через MPSC-mbox (5)
132
...и только затем их можно будет связать друг с
другом.
pongs received: 594540
pings received: 594540
Ping-pong через MPSC-mbox. Результат
133
Ubuntu 16.04 LTS, x86_64
Intel(R) Core(TM) i7-3667U CPU @ 2.00GHz
GCC 5.5.0
pongs received: 594540
pings received: 594540
Ping-pong через MPSC-mbox. Результат
134
Ubuntu 16.04 LTS, x86_64
Intel(R) Core(TM) i7-3667U CPU @ 2.00GHz
GCC 5.5.0
В случае MPMC было ~460K сообщений в одну сторону.
В случае MPSC стало ~600K сообщений.
Откуда такой разрыв?
pongs received: 594540
pings received: 594540
Ping-pong через MPSC-mbox. Результат
135
Ubuntu 16.04 LTS, x86_64
Intel(R) Core(TM) i7-3667U CPU @ 2.00GHz
GCC 5.5.0
В случае MPMC было ~460K сообщений в одну сторону.
В случае MPSC стало ~600K сообщений.
Откуда такой разрыв?
В случае с MPMC-mbox-ами есть
накладные расходы на поиск подписок в
самом mbox-е.
В случае с MPSC-mbox-ами таких
накладных расходов нет.
А что, если поместить pinger-а и ponger-а на одну общую нить?
*_one_thread
136
int main() {
so_5::launch([](so_5::environment_t & env) {
env.introduce_coop("pinger-ponger",
so_5::disp::active_obj::create_private_disp(env)->binder(),
so_5::disp::one_thread::create_private_disp(env)->binder(),
[](so_5::coop_t & coop) {
auto a_pinger = coop.make_agent<pinger>();
auto a_ponger = coop.make_agent<ponger>();
a_pinger->set_ponger_mbox(a_ponger->so_direct_mbox());
a_ponger->set_pinger_mbox(a_pinger->so_direct_mbox());
} );
std::this_thread::sleep_for(std::chrono::seconds{1});
env.stop();
});
}
*_one_thread
137
pongs received: 2486948
pings received: 2486949
*_one_thread. Результат
138
Ubuntu 16.04 LTS, x86_64
Intel(R) Core(TM) i7-3667U CPU @ 2.00GHz
GCC 5.5.0
pongs received: 2486948
pings received: 2486949
*_one_thread. Результат
139
Ubuntu 16.04 LTS, x86_64
Intel(R) Core(TM) i7-3667U CPU @ 2.00GHz
GCC 5.5.0
~2.5M сообщений в одну сторону вместо ~600K.
Почему так?
Когда pinger и ponger работают на разных рабочих нитях:
*_one_thread. Пояснение
140
pinger ponger
Когда pinger и ponger работают на разных рабочих нитях:
*_one_thread. Пояснение
141
pinger ponger
Моменты, когда входящая очередь пуста и
нить вынуждена ждать на пустой очереди.
Хватит синтетики!
Более-менее реальный пример
142
Простой Web-сервис, который масштабирует и перекодирует
картинки.
https://bitbucket.org/sobjectizerteam/shrimp-demo
https://github.com/Stiffstream/shrimp-demo
https://habr.com/post/416387/
https://habr.com/post/417527/
https://habr.com/post/420353/
Демо-проект Shrimp (1)
143
SObjectizer используется для распараллеливания обработки
запросов.
Агенты двух типов:
a_transform_manager_t
a_transformer_t
Демо-проект Shrimp (2)
144
Демо-проект Shrimp (3)
145
void a_transformer_t::so_define_agent()
{
so_subscribe_self().event(
&a_transformer_t::on_resize_request );
}
Демо-проект Shrimp (4)
146
void a_transform_manager_t::so_define_agent()
{
so_subscribe_self()
.event( &a_transform_manager_t::on_resize_request )
.event( &a_transform_manager_t::on_resize_result )
.event( &a_transform_manager_t::on_delete_cache_request )
.event( &a_transform_manager_t::on_negative_delete_cache_response )
.event( &a_transform_manager_t::on_clear_cache )
.event( &a_transform_manager_t::on_check_pending_requests );
}
Демо-проект Shrimp (5)
147
void a_transform_manager_t::so_define_agent()
{
so_subscribe_self()
.event( &a_transform_manager_t::on_resize_request )
.event( &a_transform_manager_t::on_resize_result )
.event( &a_transform_manager_t::on_delete_cache_request )
.event( &a_transform_manager_t::on_negative_delete_cache_response )
.event( &a_transform_manager_t::on_clear_cache )
.event( &a_transform_manager_t::on_check_pending_requests );
}
Демо-проект Shrimp (5)
148
Изначально этой функциональности даже
не предполагалось. Она появилась в
результате работы над Shrimp-ом.
Итоги?
Что можно сказать после 16-ти лет
в обнимку с SObjectizer?
149
Которые уже произносились неоднократно. На C++Russia 2017:
Традиционные банальности
150
Не для всех задач Модель Акторов применима.
Но там, где применима удобный фреймворк рулит и бибикает.
Тем более, что SObjectizer ‒ это не только Actor Model, но и Pub-Sub, и
CSP.
К хорошему быстро привыкаешь
151
Модель акторов имеет смысл использовать и в C++.
Нет надобности велосипедить. Есть из чего выбирать.
Название правильного фреймворка начинается на SObjec... ;)
Итого:
152
SObjectizer: https://stiffstream.com/ru/products/sobjectizer.html
Документация по SObjectizer: https://sourceforge.net/p/sobjectizer/wiki/Home/
Статьи о SObjectizer на русском: https://habr.com/users/eao197/posts
Серия презентаций о SObjectizer на английском "Dive into SObjectizer-5.5":
Intro, Agent's States, More About Coops, Exceptions, Timers, Synchonous
Interaction, Message Limits, Dispatchers, Message Chains, Mutable Messages.
Если вам нужна помощь по SObjectizer: https://stiffstream.com
153

Contenu connexe

Similaire à Акторы в C++: взгляд старого практикующего актородела (St. Petersburg C++ User Group Meetup, Aug 2018)

шишки, набитые за 15 лет использования акторов в c++ v.001.3
шишки, набитые за 15 лет использования акторов в c++ v.001.3шишки, набитые за 15 лет использования акторов в c++ v.001.3
шишки, набитые за 15 лет использования акторов в c++ v.001.3corehard_by
 
C++ CoreHard Autumn 2018. Actors vs CSP vs Tasks vs ... - Евгений Охотников
C++ CoreHard Autumn 2018. Actors vs CSP vs Tasks vs ... - Евгений ОхотниковC++ CoreHard Autumn 2018. Actors vs CSP vs Tasks vs ... - Евгений Охотников
C++ CoreHard Autumn 2018. Actors vs CSP vs Tasks vs ... - Евгений Охотниковcorehard_by
 
3D пойнтер
3D  пойнтер3D  пойнтер
3D пойнтерEligoVision
 
SECON'2016. Чубарь Алексей, Мобильные грабли Unity
SECON'2016. Чубарь Алексей, Мобильные грабли UnitySECON'2016. Чубарь Алексей, Мобильные грабли Unity
SECON'2016. Чубарь Алексей, Мобильные грабли UnitySECON
 
Как не подавиться большим старым проектом. Юрий Минаев ➠ CoreHard Autumn 2019
Как не подавиться большим старым проектом. Юрий Минаев ➠  CoreHard Autumn 2019Как не подавиться большим старым проектом. Юрий Минаев ➠  CoreHard Autumn 2019
Как не подавиться большим старым проектом. Юрий Минаев ➠ CoreHard Autumn 2019corehard_by
 
Как не подавиться большим старым проектом
Как не подавиться большим старым проектомКак не подавиться большим старым проектом
Как не подавиться большим старым проектомAndrey Karpov
 
Для чего мы делали свой акторный фреймворк и что из этого вышло?
Для чего мы делали свой акторный фреймворк и что из этого вышло?Для чего мы делали свой акторный фреймворк и что из этого вышло?
Для чего мы делали свой акторный фреймворк и что из этого вышло?Yauheni Akhotnikau
 
Кирилл Толкачев, Александр Тарасов, Хипстеры в энтерпрайзе. Шагаем в ногу со ...
Кирилл Толкачев, Александр Тарасов, Хипстеры в энтерпрайзе. Шагаем в ногу со ...Кирилл Толкачев, Александр Тарасов, Хипстеры в энтерпрайзе. Шагаем в ногу со ...
Кирилл Толкачев, Александр Тарасов, Хипстеры в энтерпрайзе. Шагаем в ногу со ...ScrumTrek
 
Attacks against machine learning algorithms
Attacks against machine learning algorithmsAttacks against machine learning algorithms
Attacks against machine learning algorithmsbeched
 
Омар Ганиев (Россия). Обзор атак на модели машинного обучения
Омар Ганиев (Россия). Обзор атак на модели машинного обученияОмар Ганиев (Россия). Обзор атак на модели машинного обучения
Омар Ганиев (Россия). Обзор атак на модели машинного обученияKazHackStan
 
2013-03-02 02 Дмитрий Пашкевич. Код на стероидах
2013-03-02 02 Дмитрий Пашкевич. Код на стероидах2013-03-02 02 Дмитрий Пашкевич. Код на стероидах
2013-03-02 02 Дмитрий Пашкевич. Код на стероидахОмские ИТ-субботники
 
К стратегической сессии по будущему интернета
К стратегической сессии по будущему интернетаК стратегической сессии по будущему интернета
К стратегической сессии по будущему интернетаAnatoly Levenchuk
 
модель акторов и C++ что, зачем и как ?
модель акторов и C++ что, зачем и как ?модель акторов и C++ что, зачем и как ?
модель акторов и C++ что, зачем и как ?corehard_by
 
Построение систем автоматического протоколирования Си/Си++ кода
Построение систем автоматического протоколирования Си/Си++ кодаПостроение систем автоматического протоколирования Си/Си++ кода
Построение систем автоматического протоколирования Си/Си++ кодаTatyanazaxarova
 
Разница в подходах анализа кода компилятором и выделенным инструментом
Разница в подходах анализа кода компилятором и выделенным инструментомРазница в подходах анализа кода компилятором и выделенным инструментом
Разница в подходах анализа кода компилятором и выделенным инструментомTatyanazaxarova
 
"Без, платно: виды монетизации через Freemium в мобильной индустрии", Леонид ...
"Без, платно: виды монетизации через Freemium в мобильной индустрии", Леонид ..."Без, платно: виды монетизации через Freemium в мобильной индустрии", Леонид ...
"Без, платно: виды монетизации через Freemium в мобильной индустрии", Леонид ...Julia Lebedeva
 
DesignCamp2012: Юрий Ветров — Метро-дизайн в Mail.Ru
DesignCamp2012: Юрий Ветров — Метро-дизайн в Mail.RuDesignCamp2012: Юрий Ветров — Метро-дизайн в Mail.Ru
DesignCamp2012: Юрий Ветров — Метро-дизайн в Mail.RuYury Vetrov
 
Alexey Savchenko, Evangelist, Unreal Engine/ Epic Games
Alexey Savchenko, Evangelist, Unreal Engine/ Epic GamesAlexey Savchenko, Evangelist, Unreal Engine/ Epic Games
Alexey Savchenko, Evangelist, Unreal Engine/ Epic GamesWhite Nights Conference
 
R0boCamp2016 Євген Лохматов: IoT: проблеми і рішення, світові тренди
R0boCamp2016 Євген Лохматов: IoT: проблеми і рішення, світові трендиR0boCamp2016 Євген Лохматов: IoT: проблеми і рішення, світові тренди
R0boCamp2016 Євген Лохматов: IoT: проблеми і рішення, світові трендиLviv Startup Club
 
Хипстеры в энтерпрайзе
Хипстеры в энтерпрайзеХипстеры в энтерпрайзе
Хипстеры в энтерпрайзеAleksandr Tarasov
 

Similaire à Акторы в C++: взгляд старого практикующего актородела (St. Petersburg C++ User Group Meetup, Aug 2018) (20)

шишки, набитые за 15 лет использования акторов в c++ v.001.3
шишки, набитые за 15 лет использования акторов в c++ v.001.3шишки, набитые за 15 лет использования акторов в c++ v.001.3
шишки, набитые за 15 лет использования акторов в c++ v.001.3
 
C++ CoreHard Autumn 2018. Actors vs CSP vs Tasks vs ... - Евгений Охотников
C++ CoreHard Autumn 2018. Actors vs CSP vs Tasks vs ... - Евгений ОхотниковC++ CoreHard Autumn 2018. Actors vs CSP vs Tasks vs ... - Евгений Охотников
C++ CoreHard Autumn 2018. Actors vs CSP vs Tasks vs ... - Евгений Охотников
 
3D пойнтер
3D  пойнтер3D  пойнтер
3D пойнтер
 
SECON'2016. Чубарь Алексей, Мобильные грабли Unity
SECON'2016. Чубарь Алексей, Мобильные грабли UnitySECON'2016. Чубарь Алексей, Мобильные грабли Unity
SECON'2016. Чубарь Алексей, Мобильные грабли Unity
 
Как не подавиться большим старым проектом. Юрий Минаев ➠ CoreHard Autumn 2019
Как не подавиться большим старым проектом. Юрий Минаев ➠  CoreHard Autumn 2019Как не подавиться большим старым проектом. Юрий Минаев ➠  CoreHard Autumn 2019
Как не подавиться большим старым проектом. Юрий Минаев ➠ CoreHard Autumn 2019
 
Как не подавиться большим старым проектом
Как не подавиться большим старым проектомКак не подавиться большим старым проектом
Как не подавиться большим старым проектом
 
Для чего мы делали свой акторный фреймворк и что из этого вышло?
Для чего мы делали свой акторный фреймворк и что из этого вышло?Для чего мы делали свой акторный фреймворк и что из этого вышло?
Для чего мы делали свой акторный фреймворк и что из этого вышло?
 
Кирилл Толкачев, Александр Тарасов, Хипстеры в энтерпрайзе. Шагаем в ногу со ...
Кирилл Толкачев, Александр Тарасов, Хипстеры в энтерпрайзе. Шагаем в ногу со ...Кирилл Толкачев, Александр Тарасов, Хипстеры в энтерпрайзе. Шагаем в ногу со ...
Кирилл Толкачев, Александр Тарасов, Хипстеры в энтерпрайзе. Шагаем в ногу со ...
 
Attacks against machine learning algorithms
Attacks against machine learning algorithmsAttacks against machine learning algorithms
Attacks against machine learning algorithms
 
Омар Ганиев (Россия). Обзор атак на модели машинного обучения
Омар Ганиев (Россия). Обзор атак на модели машинного обученияОмар Ганиев (Россия). Обзор атак на модели машинного обучения
Омар Ганиев (Россия). Обзор атак на модели машинного обучения
 
2013-03-02 02 Дмитрий Пашкевич. Код на стероидах
2013-03-02 02 Дмитрий Пашкевич. Код на стероидах2013-03-02 02 Дмитрий Пашкевич. Код на стероидах
2013-03-02 02 Дмитрий Пашкевич. Код на стероидах
 
К стратегической сессии по будущему интернета
К стратегической сессии по будущему интернетаК стратегической сессии по будущему интернета
К стратегической сессии по будущему интернета
 
модель акторов и C++ что, зачем и как ?
модель акторов и C++ что, зачем и как ?модель акторов и C++ что, зачем и как ?
модель акторов и C++ что, зачем и как ?
 
Построение систем автоматического протоколирования Си/Си++ кода
Построение систем автоматического протоколирования Си/Си++ кодаПостроение систем автоматического протоколирования Си/Си++ кода
Построение систем автоматического протоколирования Си/Си++ кода
 
Разница в подходах анализа кода компилятором и выделенным инструментом
Разница в подходах анализа кода компилятором и выделенным инструментомРазница в подходах анализа кода компилятором и выделенным инструментом
Разница в подходах анализа кода компилятором и выделенным инструментом
 
"Без, платно: виды монетизации через Freemium в мобильной индустрии", Леонид ...
"Без, платно: виды монетизации через Freemium в мобильной индустрии", Леонид ..."Без, платно: виды монетизации через Freemium в мобильной индустрии", Леонид ...
"Без, платно: виды монетизации через Freemium в мобильной индустрии", Леонид ...
 
DesignCamp2012: Юрий Ветров — Метро-дизайн в Mail.Ru
DesignCamp2012: Юрий Ветров — Метро-дизайн в Mail.RuDesignCamp2012: Юрий Ветров — Метро-дизайн в Mail.Ru
DesignCamp2012: Юрий Ветров — Метро-дизайн в Mail.Ru
 
Alexey Savchenko, Evangelist, Unreal Engine/ Epic Games
Alexey Savchenko, Evangelist, Unreal Engine/ Epic GamesAlexey Savchenko, Evangelist, Unreal Engine/ Epic Games
Alexey Savchenko, Evangelist, Unreal Engine/ Epic Games
 
R0boCamp2016 Євген Лохматов: IoT: проблеми і рішення, світові тренди
R0boCamp2016 Євген Лохматов: IoT: проблеми і рішення, світові трендиR0boCamp2016 Євген Лохматов: IoT: проблеми і рішення, світові тренди
R0boCamp2016 Євген Лохматов: IoT: проблеми і рішення, світові тренди
 
Хипстеры в энтерпрайзе
Хипстеры в энтерпрайзеХипстеры в энтерпрайзе
Хипстеры в энтерпрайзе
 

Plus de Yauheni Akhotnikau

arataga. SObjectizer and RESTinio in action: a real-world example
arataga. SObjectizer and RESTinio in action: a real-world examplearataga. SObjectizer and RESTinio in action: a real-world example
arataga. SObjectizer and RESTinio in action: a real-world exampleYauheni Akhotnikau
 
Actor Model and C++: what, why and how? (March 2020 Edition)
Actor Model and C++: what, why and how? (March 2020 Edition)Actor Model and C++: what, why and how? (March 2020 Edition)
Actor Model and C++: what, why and how? (March 2020 Edition)Yauheni Akhotnikau
 
What is SObjectizer 5.7 (at v.5.7.0)
What is SObjectizer 5.7 (at v.5.7.0)What is SObjectizer 5.7 (at v.5.7.0)
What is SObjectizer 5.7 (at v.5.7.0)Yauheni Akhotnikau
 
What is SObjectizer 5.6 (at v.5.6.0)
What is SObjectizer 5.6 (at v.5.6.0)What is SObjectizer 5.6 (at v.5.6.0)
What is SObjectizer 5.6 (at v.5.6.0)Yauheni Akhotnikau
 
[C++ CoreHard Autumn 2018] Actors vs CSP vs Task...
[C++ CoreHard Autumn 2018] Actors vs CSP vs Task...[C++ CoreHard Autumn 2018] Actors vs CSP vs Task...
[C++ CoreHard Autumn 2018] Actors vs CSP vs Task...Yauheni Akhotnikau
 
Shrimp: A Rather Practical Example Of Application Development With RESTinio a...
Shrimp: A Rather Practical Example Of Application Development With RESTinio a...Shrimp: A Rather Practical Example Of Application Development With RESTinio a...
Shrimp: A Rather Practical Example Of Application Development With RESTinio a...Yauheni Akhotnikau
 
Акторы на C++: стоило ли оно того?
Акторы на C++: стоило ли оно того?Акторы на C++: стоило ли оно того?
Акторы на C++: стоило ли оно того?Yauheni Akhotnikau
 
25 Years of C++ History Flashed in Front of My Eyes
25 Years of C++ History Flashed in Front of My Eyes25 Years of C++ History Flashed in Front of My Eyes
25 Years of C++ History Flashed in Front of My EyesYauheni Akhotnikau
 
GECon 2017: C++ - a Monster that no one likes but that will outlast them all
GECon 2017: C++ - a Monster that no one likes but that will outlast them allGECon 2017: C++ - a Monster that no one likes but that will outlast them all
GECon 2017: C++ - a Monster that no one likes but that will outlast them allYauheni Akhotnikau
 
Dive into SObjectizer 5.5. Tenth part: Mutable Messages
Dive into SObjectizer 5.5. Tenth part: Mutable MessagesDive into SObjectizer 5.5. Tenth part: Mutable Messages
Dive into SObjectizer 5.5. Tenth part: Mutable MessagesYauheni Akhotnikau
 
Actor Model and C++: what, why and how?
Actor Model and C++: what, why and how?Actor Model and C++: what, why and how?
Actor Model and C++: what, why and how?Yauheni Akhotnikau
 
Модель акторов и C++ что, зачем и как?
Модель акторов и C++ что, зачем и как?Модель акторов и C++ что, зачем и как?
Модель акторов и C++ что, зачем и как?Yauheni Akhotnikau
 
Dive into SObjectizer 5.5. Ninth Part: Message Chains
Dive into SObjectizer 5.5. Ninth Part: Message ChainsDive into SObjectizer 5.5. Ninth Part: Message Chains
Dive into SObjectizer 5.5. Ninth Part: Message ChainsYauheni Akhotnikau
 
Dive into SObjectizer 5.5. Eighth Part: Dispatchers
Dive into SObjectizer 5.5. Eighth Part: DispatchersDive into SObjectizer 5.5. Eighth Part: Dispatchers
Dive into SObjectizer 5.5. Eighth Part: DispatchersYauheni Akhotnikau
 
What's new in SObjectizer 5.5.9
What's new in SObjectizer 5.5.9What's new in SObjectizer 5.5.9
What's new in SObjectizer 5.5.9Yauheni Akhotnikau
 
Dive into SObjectizer 5.5. Seventh part: Message Limits
Dive into SObjectizer 5.5. Seventh part: Message LimitsDive into SObjectizer 5.5. Seventh part: Message Limits
Dive into SObjectizer 5.5. Seventh part: Message LimitsYauheni Akhotnikau
 
Dive into SObjectizer-5.5. Sixth part: Synchronous Interaction
Dive into SObjectizer-5.5. Sixth part: Synchronous InteractionDive into SObjectizer-5.5. Sixth part: Synchronous Interaction
Dive into SObjectizer-5.5. Sixth part: Synchronous InteractionYauheni Akhotnikau
 
Dive into SObjectizer 5.5. Fifth part: Timers
Dive into SObjectizer 5.5. Fifth part: TimersDive into SObjectizer 5.5. Fifth part: Timers
Dive into SObjectizer 5.5. Fifth part: TimersYauheni Akhotnikau
 
What’s new in SObjectizer 5.5.8
What’s new in SObjectizer 5.5.8What’s new in SObjectizer 5.5.8
What’s new in SObjectizer 5.5.8Yauheni Akhotnikau
 
Dive into SObjectizer 5.5. Fourth part. Exception
Dive into SObjectizer 5.5. Fourth part. ExceptionDive into SObjectizer 5.5. Fourth part. Exception
Dive into SObjectizer 5.5. Fourth part. ExceptionYauheni Akhotnikau
 

Plus de Yauheni Akhotnikau (20)

arataga. SObjectizer and RESTinio in action: a real-world example
arataga. SObjectizer and RESTinio in action: a real-world examplearataga. SObjectizer and RESTinio in action: a real-world example
arataga. SObjectizer and RESTinio in action: a real-world example
 
Actor Model and C++: what, why and how? (March 2020 Edition)
Actor Model and C++: what, why and how? (March 2020 Edition)Actor Model and C++: what, why and how? (March 2020 Edition)
Actor Model and C++: what, why and how? (March 2020 Edition)
 
What is SObjectizer 5.7 (at v.5.7.0)
What is SObjectizer 5.7 (at v.5.7.0)What is SObjectizer 5.7 (at v.5.7.0)
What is SObjectizer 5.7 (at v.5.7.0)
 
What is SObjectizer 5.6 (at v.5.6.0)
What is SObjectizer 5.6 (at v.5.6.0)What is SObjectizer 5.6 (at v.5.6.0)
What is SObjectizer 5.6 (at v.5.6.0)
 
[C++ CoreHard Autumn 2018] Actors vs CSP vs Task...
[C++ CoreHard Autumn 2018] Actors vs CSP vs Task...[C++ CoreHard Autumn 2018] Actors vs CSP vs Task...
[C++ CoreHard Autumn 2018] Actors vs CSP vs Task...
 
Shrimp: A Rather Practical Example Of Application Development With RESTinio a...
Shrimp: A Rather Practical Example Of Application Development With RESTinio a...Shrimp: A Rather Practical Example Of Application Development With RESTinio a...
Shrimp: A Rather Practical Example Of Application Development With RESTinio a...
 
Акторы на C++: стоило ли оно того?
Акторы на C++: стоило ли оно того?Акторы на C++: стоило ли оно того?
Акторы на C++: стоило ли оно того?
 
25 Years of C++ History Flashed in Front of My Eyes
25 Years of C++ History Flashed in Front of My Eyes25 Years of C++ History Flashed in Front of My Eyes
25 Years of C++ History Flashed in Front of My Eyes
 
GECon 2017: C++ - a Monster that no one likes but that will outlast them all
GECon 2017: C++ - a Monster that no one likes but that will outlast them allGECon 2017: C++ - a Monster that no one likes but that will outlast them all
GECon 2017: C++ - a Monster that no one likes but that will outlast them all
 
Dive into SObjectizer 5.5. Tenth part: Mutable Messages
Dive into SObjectizer 5.5. Tenth part: Mutable MessagesDive into SObjectizer 5.5. Tenth part: Mutable Messages
Dive into SObjectizer 5.5. Tenth part: Mutable Messages
 
Actor Model and C++: what, why and how?
Actor Model and C++: what, why and how?Actor Model and C++: what, why and how?
Actor Model and C++: what, why and how?
 
Модель акторов и C++ что, зачем и как?
Модель акторов и C++ что, зачем и как?Модель акторов и C++ что, зачем и как?
Модель акторов и C++ что, зачем и как?
 
Dive into SObjectizer 5.5. Ninth Part: Message Chains
Dive into SObjectizer 5.5. Ninth Part: Message ChainsDive into SObjectizer 5.5. Ninth Part: Message Chains
Dive into SObjectizer 5.5. Ninth Part: Message Chains
 
Dive into SObjectizer 5.5. Eighth Part: Dispatchers
Dive into SObjectizer 5.5. Eighth Part: DispatchersDive into SObjectizer 5.5. Eighth Part: Dispatchers
Dive into SObjectizer 5.5. Eighth Part: Dispatchers
 
What's new in SObjectizer 5.5.9
What's new in SObjectizer 5.5.9What's new in SObjectizer 5.5.9
What's new in SObjectizer 5.5.9
 
Dive into SObjectizer 5.5. Seventh part: Message Limits
Dive into SObjectizer 5.5. Seventh part: Message LimitsDive into SObjectizer 5.5. Seventh part: Message Limits
Dive into SObjectizer 5.5. Seventh part: Message Limits
 
Dive into SObjectizer-5.5. Sixth part: Synchronous Interaction
Dive into SObjectizer-5.5. Sixth part: Synchronous InteractionDive into SObjectizer-5.5. Sixth part: Synchronous Interaction
Dive into SObjectizer-5.5. Sixth part: Synchronous Interaction
 
Dive into SObjectizer 5.5. Fifth part: Timers
Dive into SObjectizer 5.5. Fifth part: TimersDive into SObjectizer 5.5. Fifth part: Timers
Dive into SObjectizer 5.5. Fifth part: Timers
 
What’s new in SObjectizer 5.5.8
What’s new in SObjectizer 5.5.8What’s new in SObjectizer 5.5.8
What’s new in SObjectizer 5.5.8
 
Dive into SObjectizer 5.5. Fourth part. Exception
Dive into SObjectizer 5.5. Fourth part. ExceptionDive into SObjectizer 5.5. Fourth part. Exception
Dive into SObjectizer 5.5. Fourth part. Exception
 

Акторы в C++: взгляд старого практикующего актородела (St. Petersburg C++ User Group Meetup, Aug 2018)

  • 1. St.Petersburg C++ User Group Акторы в C++: взгляд старого практикующего актородела Евгений Охотников
  • 2. Коротко о себе Профессиональный разработчик с 1994-го. ● 1994-2000: АСУ ТП/SCADA и т.п.; ● 2001-2014: телеком, платежные сервисы; ● 2014-...: OpenSource-инструментарий для C++ "Тот самый eao197" (с) RSDN Автор блога "Размышлизмы eao197" (http://eao197.blogspot.com/) Сооснователь stiffstream.com 2
  • 4. Многопоточность – это... инструмент, который активно применяется в двух очень разных областях: ● parallel computing; ● concurrent computing. Concurrent computing может быть и без многопоточности. Будем говорить о concurrent computing. 4
  • 6. Изменяемое разделяемое состояние Главный фактор сложности в многопоточном программировании. 6
  • 7. Убрать разделяемое состояние Ничего не разделяем. Ни за что не боремся. Пусть каждый поток владеет своими собственными данными. И никто кроме потока-владельца не имеет к этим данным доступа. Принцип shared nothing (широко известен в узких кругах). https://en.wikipedia.org/wiki/Shared_nothing_architecture 7
  • 8. Потокам нужно общаться, но как? Кажется, что есть два способа: 1. Синхронно. 2. Асинхронно. Но это только кажется... Синхронное взаимодействие – это путь в никуда. 8
  • 10. Просто же! Есть потоки. У каждого есть очередь входящих сообщений. Поток спит, если очередь входящих сообщений пуста. Поток просыпается, когда для него появляются входящие сообщения. Потоки общаются только через асинхронные сообщения. 10
  • 11. Дополнительный бонус Если сообщения несут копию данных, то мы получаем очень приятный бонус: прозрачный переход к распределенности. 11
  • 12. Модель Акторов. Основные принципы ● актор – это некая сущность, обладающая поведением; ● акторы реагируют на входящие сообщения; ● получив сообщение актор может: ○ отослать некоторое (конечное) количество сообщений другим акторам; ○ создать некоторое (конечное) количество новых акторов; ○ определить для себя новое поведение для обработки последующих сообщений. 12
  • 13. Актор – это некоторая сущность Не более того. Актором может быть отдельный процесс. Например, Erlang. Актором может быть отдельный поток (OS thread, "green" thread, fiber). Например, goroutine в Go может рассматриваться как актор. Актором может быть объект, которому кто-то выделяет рабочий контекст. Например, Akka. 13
  • 14. Модель Акторов (официоз №1) Появилась в 1973-ем году благодаря работам Карла Хьюитта (Carl Hewitt). Была затем развита в 1981-ом Уильямом Клингером (William Clinger). И в 1985-ом Гулом Агха (Gul Agha). Это если говорить про формализацию Модели Акторов. Неформально она открывалась и переоткрывалась множество раз. 14
  • 15. Модель Акторов (официоз №2) Отправные точки для желающих погрузиться в формальную теорию Модели Акторов: https://en.wikipedia.org/wiki/History_of_the_Actor_model https://en.wikipedia.org/wiki/Actor_model https://en.wikipedia.org/wiki/Actor_model_theory 15
  • 16. Вопрос ребром! Почему Модель Акторов привлекает внимание? 16
  • 17. Чаще всего упоминается: 1. Простота. Нет разделяемых данных. Асинхронный обмен сообщениями. 2. Аналогии с реальным миром. Многие вещи знакомы и понятны. 3. Масштабирование. Акторов может быть хоть миллион. Акторы могут жить на разных нодах. 4. Отказоустойчивость. Какие-то акторы могут "падать", другие акторы это обнаружат и исправят (см. систему супервизоров Erlang-а). Shared nothing + message-passing сильно помогают и тут. 17
  • 18. Модель Акторов как... ...способ смотреть на мир. Другими словами: если у вас в руках молоток... 18
  • 19. Вопрос на миллион Модель Акторов ‒ это серебряная пуля? 19
  • 20. Границы применимости Есть несколько маркеров, которые могут подсказать, что выбор Модели Акторов будет оправданным. Могут подсказать. А могут и не подсказать. 20
  • 22. Fire-and-Forget 1. Тотальный контроль не нужен. 2. Результаты начатых операций вот прямо здесь и сейчас не нужны. 3. Если что-то не случилось, то ничего страшного. Мы постоянно используем этот принцип в повседневной жизни 22
  • 23. Fire-and-Forget: примеры "за" и "против" Пример №1: две рабочие нити, одна только читает данные, вторая только разбирает их. Пример №2: коммит в БД. 23
  • 25. Принципы Модели Акторов еще раз ● актор – это некая сущность, обладающая поведением; ● акторы реагируют на входящие сообщения; ● получив сообщение актор может: ○ отослать некоторое (конечное) количество сообщений другим акторам; ○ создать некоторое (конечное) количество новых акторов; ○ определить для себя новое поведение для обработки последующих сообщений. 25
  • 26. О каких конечных автоматах речь? 26
  • 28. Shared Nothing: контрольный в голову Могут ли сущности в программе работать без каких либо разделяемых данных? Может ли каждая сущность быть представлена в виде отдельного процесса? Это таки экстремизм. Но он отрезвляет. 28
  • 29. Не маркер, но тем не менее Таймеры 29
  • 30. Напрямую не связано с Моделью Акторов, но... ...таймеры в виде отложенных сообщений ‒ это очень удобно на практике. Следствие того, что акторы общаются с внешним миром только посредством сообщений. Таймеры активно используются акторами из-за принципа Fire-and-Forget. 30
  • 31. Общие слова закончились... ...надеюсь, что пока было понятно 31
  • 32. Ближе к телу! Модель Акторов и C++: миф или реальность? 32
  • 33. Реальность! ● промышленная автоматизация (АСУ ТП); ● телеком; ● электронная и мобильная коммерция; ● имитационное моделирование; ● САПР; ● игростроение; ● ПО промежуточного слоя (СУБД, ...) 33
  • 34. Есть несколько нюансов Вызванных спецификой C++: ● небезопасностью; ● специфическими прикладными нишами (embedded, high-performance, real-time и т.д.). В C++ все акторные фреймворки очень разные и непохожие. 34
  • 35. Есть готовые инструменты: Наиболее известные: ● QP/C++ (двойная лицензия); ● Just::Thread Pro: Actors Edition (коммерческая лицензия); ● С++ Actor Framework (BSD-3-Clause лицензия); ● SObjectizer (BSD-3-Clause лицензия); Кроме того: ● OOSMOS (двойная лицензия, язык C, но может использоваться и в C++); ● Asyncronous Agents Library (входит в Visual Studio). Более-менее актуальный список в Wikipedia. 35
  • 36. А теперь о главном SObjectizer 36
  • 37. Давным-давно, в далекой-далекой... 1994-й год. Гомель. КБ Системного Программирования. Отдел АСУТП. Попытка сделать объектно-ориентированную SCADA-систему. 37
  • 38. Объектная SCADA. Итог SCADA Objectizer. 1998-й год. Опробован в реальном проекте. Прекратил свое существование в начале 2000-го :( 38
  • 39. Предпосылки к перерождению Начало 2002-го. Компания "Интервэйл". Двое участников разработки SCADA Objectizer. Небольшая GUI-программа для управления подключенными к ПК устройствами... 39
  • 40. Апрель 2002-го года. SObjectizer = SCADA + Objectizer. SObjectizer-4 (в четвертый раз все сначала). Четвертое пришествие 40
  • 41. Май 2002-го: начало использования SO-4 в разработке. Март 2006-го: перевод SO-4 в категорию OpenSource проектов под BSD-лицензией. Сентябрь 2010-го: начало работ над SObjectizer-5. Май 2013-го: публичный релиз SO-5 под BSD-лицензией. Июль 2013-го: SObjectizer начал жить независимо от "Интервэйл". Этапы большого пути 41
  • 42. В компании "Интервэйл": ● SMS/USSD-агрегатор; ● куски платежных сервисов, платформ электронной коммерции; ● система мониторинга для DevOps. Вне "Интервэйла": ● имитационное моделирование; ● тестовые стенды; ● управление оборудованием; ● прототипирование. Применение 42
  • 43. SObjectizer v.5.5.22.1: ● май 2018; ● ~31 KLOC кода; ● ~35 KLOC тестов; ● ~10 KLOC примеров (работающих). С++11. Минимальные требования: GCC-4.8, VC++ 12.0, clang-3.5 Платформы: Windows, Linux, FreeBSD, macOS, Android (CrystaX NDK). SObjectizer-5: факты и цифры 43
  • 44. ...мы не делали реализацию Модели Акторов. На самом деле... 44
  • 45. ...упростить для себя разработку многопоточного кода. На момент начала работ над SObjectizer мы даже не знали про Модель Акторов вообще. Но получилась одна из возможных реализаций Модели Акторов. Мы лишь хотели... 45
  • 46. Хватит общих слов! Что мы получили в SObjectizer-5? 46
  • 47. Агенты. Кооперации агентов. (Агент == Актор и наоборот) Сообщения. Подписки на сообщения. Почтовые ящики. Диспетчеры. Из чего же, из чего же, из чего же... 47
  • 48. Все взаимодействие между агентами в SObjectizer идет только через асинхронные сообщения. Сообщения это объекты. Тип сообщения ‒ это ключ для поиска обработчиков сообщения. В SObjectizer есть сообщения 48
  • 49. 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) {} }; Выглядят сообщения вот так: 49
  • 50. 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) {} }; Выглядят сообщения вот так: 50 Почему именно struct/class, а не tuple или что-то еще? Структура гораздо проще эволюционирует. Добавляем-удаляем-изменяем поле структуры и не нужно модифицировать объявления обработчиков сообщения.
  • 51. 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) {} }; Выглядят сообщения вот так: 51 Почему именно struct/class, а не tuple или что-то еще? Структура гораздо проще эволюционирует. Добавляем-удаляем-изменяем поле структуры и не нужно модифицировать объявления обработчиков сообщения. struct reconfigure { const std::string cfg_file_; ... }; void my_agent::so_define_agent() { so_subscribe_self().event(&my_agent::on_reconfigure); ... } void my_agent::on_reconfigure(mhood_t<reconfigure> cmd) { ... }
  • 52. 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) {} }; Выглядят сообщения вот так: 52 Почему именно struct/class, а не tuple или что-то еще? Структура гораздо проще эволюционирует. Добавляем-удаляем-изменяем поле структуры и не нужно модифицировать объявления обработчиков сообщения. struct reconfigure { const std::string cfg_file_; ... }; void my_agent::so_define_agent() { so_subscribe_self().event(&my_agent::on_reconfigure); ... } void my_agent::on_reconfigure(mhood_t<reconfigure> cmd) { ... } struct reconfigure { enum class on_error_action { use_current, use_defaults, abort }; const std::string cfg_file_; on_error_action on_error_; ... }; void my_agent::so_define_agent() { so_subscribe_self().event(&my_agent::on_reconfigure); ... } void my_agent::on_reconfigure(mhood_t<reconfigure> cmd) { ... }
  • 53. struct get_status : public so_5::signal_t {}; Еще есть сигналы 53
  • 54. // Безотлагательная отсылка сообщения. so_5::send<request>(target, // Все это через perfect-forwarding идет в конструктор request-а. make_id(), make_params(), make_payload(), calculate_deadline()); // Безотлагательная отсылка сигнала. so_5::send<get_status>(target); Отсылаются сообщения вот так: 54
  • 55. // Безотлагательная отсылка сообщения. so_5::send<request>(target, // Все это через perfect-forwarding идет в конструктор request-а. make_id(), make_params(), make_payload(), calculate_deadline()); // Безотлагательная отсылка сигнала. so_5::send<get_status>(target); Отсылаются сообщения вот так: 55 Эквивалентный код: auto msg = std::make_unique<request>( make_id(), make_params(), make_payload(), calculate_deadline()); target->deliver_message( std::move(msg) );
  • 56. В "традиционной" Модели Акторов адресатом сообщения является актор. В SObjectizer сообщения отсылаются в "почтовый ящик". Почтовый ящик в SObjectizer называется mbox. Что такое Target для send? 56
  • 58. MPMC (Multi-Producer/Multi-Consumer) любой может послать, любой может подписаться. MPSC (Multi-Producer/Single-Consumer) любой может послать, только владелец может подписаться, автоматически создается для каждого агента, так же называется direct mbox. Два типа mbox-ов 58
  • 59. MPMC (Multi-Producer/Multi-Consumer) любой может послать, любой может подписаться. MPSC (Multi-Producer/Single-Consumer) любой может послать, только владелец может подписаться, автоматически создается для каждого агента, так же называется direct mbox. Два типа mbox-ов 59 Использование MPSC mbox-ов и дает максимальное приближение к Модели Акторов.
  • 60. MPMC (Multi-Producer/Multi-Consumer) любой может послать, любой может подписаться. MPSC (Multi-Producer/Single-Consumer) любой может послать, только владелец может подписаться, автоматически создается для каждого агента, так же называется direct mbox. Два типа mbox-ов 60 Почему именно mbox-ы и почему два типа mbox-ов? ● ноги растут из мира АСУТП, там взаимодействие 1:N широко используется; ● посредством mbox-ов получаем еще и модель Pub/Sub (mbox выступает в качестве брокера, тип сообщения - в качестве имени топика); ● легко делать разные трюки (so_5::extra::mboxes::round_robin, so_5::extra::mboxes::collecting_mbox, so_5::extra::mboxes::retained_msg). Изначально в SO-5 был только один тип mbox-ов: MPMC.
  • 61. // Подписка на сообщение из mbox-а. so_subscribe(some_mbox).event(... /* обработчик */); // Подписка на сообщение из собственного MPSC-mbox-а. so_subscribe_self().event(... /* обработчик */); Подписка на сообщения 61
  • 62. // Подписка на сообщение из mbox-а. so_subscribe(some_mbox).event(... /* обработчик */); // Подписка на сообщение из собственного MPSC-mbox-а. so_subscribe_self().event(... /* обработчик */); Подписка на сообщения 62 Всего лишь более компактная запись для: so_subscribe(so_direct_mbox()).event(... /* обработчик */);
  • 63. Именно диспетчер определяет где и когда агент будет обрабатывать свои сообщения. Может быть запущено сразу несколько диспетчеров. Поэтому каждый агент должен быть привязан к какому-то диспетчеру. В SObjectizer есть диспетчеры 63
  • 64. В SObjectizer есть диспетчеры 64
  • 67. Зачем так много? 67 Иногда может потребоваться сделать диспетчер под задачу: https://habr.com/post/353712/
  • 68. Как правило, реализуются как объекты классов, унаследованных от 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(); } }; В SObjectizer есть агенты 68
  • 69. Как правило, реализуются как объекты классов, унаследованных от 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(); } }; В SObjectizer есть агенты 69 Почему классы? Почему наследование? Агенты в реальных приложениях гораздо сложнее оных из HelloWorld-овских примеров. Наглядный пример можно посмотреть здесь: http://eao197.blogspot.com/2016/05/progc14-sobjectizer.html ООП рулит и бибикает. И это не шутка. Плюс явно выраженные состояния агентов...
  • 70. Состояния позволяют реализовывать сложные иерархические конечные автоматы: ● вложенные состояния; ● on_enter/on_exit для состояний; ● time_limit для состояний; ● deep/shallow history для состояний. У агентов есть состояния 70
  • 71. Состояния позволяют реализовывать сложные иерархические конечные автоматы: ● вложенные состояния; ● on_enter/on_exit для состояний; ● time_limit для состояний; ● deep/shallow history для состояний. У агентов есть состояния 71 Опять же все потому, что корни растут из АСУТП.
  • 72. Пример агента с состояниями 72
  • 73. class player_demo final : public so_5::agent_t { state_t off{this, "off"}, on{this, "on"}, active{initial_substate_of{on}, "active"}, inactive{substate_of{on}, "inactive"}; ... Пример агента с состояниями 73
  • 74. class player_demo final : public so_5::agent_t { state_t off{this, "off"}, on{this, "on"}, active{initial_substate_of{on}, "active"}, inactive{substate_of{on}, "inactive"}; ... Пример агента с состояниями 74 Состояния самого верхнего уровня. Не имеют родительских состояний.
  • 75. class player_demo final : public so_5::agent_t { state_t off{this, "off"}, on{this, "on"}, active{initial_substate_of{on}, "active"}, inactive{substate_of{on}, "inactive"}; ... Пример агента с состояниями 75 Подсостояния состояния on.
  • 76. class player_demo final : public so_5::agent_t { state_t off{this, "off"}, on{this, "on"}, active{initial_substate_of{on}, "active"}, inactive{substate_of{on}, "inactive"}; ... Пример агента с состояниями 76 Начальное подсостояние для состояния on. Как только агент переходит в состояние on, автоматически активируется подсостояние on::active.
  • 77. void so_define_agent() override { this >>= off; off.event([&](mhood_t<turn_on_off>){... /* включить устройство */}); on.event([&](mhood_t<turn_on_off>{... /* выключить устройство */}); active .on_enter([&]{... /* зажечь экран */}) .on_exit([&]{... /* погасить экран */}) .time_limit(15s, inactive) .event([&](mhood_t<volume_up> cmd){... /* реакция на volume_up */}) .event([&](mhood_t<volume_down> cmd){... /* реакция на volume_down */}); inactive .transfer_to_state<volume_up>(active) .transfer_to_state<volume_down>(active); } Пример агента с состояниями 77
  • 78. void so_define_agent() override { this >>= off; off.event([&](mhood_t<turn_on_off>){... /* включить устройство */}); on.event([&](mhood_t<turn_on_off>{... /* выключить устройство */}); active .on_enter([&]{... /* зажечь экран */}) .on_exit([&]{... /* погасить экран */}) .time_limit(15s, inactive) .event([&](mhood_t<volume_up> cmd){... /* реакция на volume_up */}) .event([&](mhood_t<volume_down> cmd){... /* реакция на volume_down */}); inactive .transfer_to_state<volume_up>(active) .transfer_to_state<volume_down>(active); } Пример агента с состояниями 78 Агент должен начать работать в состояние off. По умолчанию агент начинает работать в специальном default_state. Поэтому нужно явно перевести агента в состояние off.
  • 79. void so_define_agent() override { this >>= off; off.event([&](mhood_t<turn_on_off>){... /* включить устройство */}); on.event([&](mhood_t<turn_on_off>{... /* выключить устройство */}); active .on_enter([&]{... /* зажечь экран */}) .on_exit([&]{... /* погасить экран */}) .time_limit(15s, inactive) .event([&](mhood_t<volume_up> cmd){... /* реакция на volume_up */}) .event([&](mhood_t<volume_down> cmd){... /* реакция на volume_down */}); inactive .transfer_to_state<volume_up>(active) .transfer_to_state<volume_down>(active); } Пример агента с состояниями 79 Агент должен начать работать в состояние off. По умолчанию агент начинает работать в специальном default_state. Поэтому нужно явно перевести агента в состояние on. Специально для тех, кто не любит подобный синтаксис. Есть альтернативные варианты записи: so_change_state(off); off.activate();
  • 80. void so_define_agent() override { this >>= off; off.event([&](mhood_t<turn_on_off>){... /* включить устройство */}); on.event([&](mhood_t<turn_on_off>{... /* выключить устройство */}); active .on_enter([&]{... /* зажечь экран */}) .on_exit([&]{... /* погасить экран */}) .time_limit(15s, inactive) .event([&](mhood_t<volume_up> cmd){... /* реакция на volume_up */}) .event([&](mhood_t<volume_down> cmd){... /* реакция на volume_down */}); inactive .transfer_to_state<volume_up>(active) .transfer_to_state<volume_down>(active); } Пример агента с состояниями 80 Подписка на событие в определенном состоянии.
  • 81. void so_define_agent() override { this >>= off; off.event([&](mhood_t<turn_on_off>){... /* включить устройство */}); on.event([&](mhood_t<turn_on_off>{... /* выключить устройство */}); active .on_enter([&]{... /* зажечь экран */}) .on_exit([&]{... /* погасить экран */}) .time_limit(15s, inactive) .event([&](mhood_t<volume_up> cmd){... /* реакция на volume_up */}) .event([&](mhood_t<volume_down> cmd){... /* реакция на volume_down */}); inactive .transfer_to_state<volume_up>(active) .transfer_to_state<volume_down>(active); } Пример агента с состояниями 81 Подписка на событие в определенном состоянии. Можно записать и по-другому: so_subscribe_self().in(off).event(...);
  • 82. void so_define_agent() override { this >>= off; off.event([&](mhood_t<turn_on_off>){... /* включить устройство */}); on.event([&](mhood_t<turn_on_off>{... /* выключить устройство */}); active .on_enter([&]{... /* зажечь экран */}) .on_exit([&]{... /* погасить экран */}) .time_limit(15s, inactive) .event([&](mhood_t<volume_up> cmd){... /* реакция на volume_up */}) .event([&](mhood_t<volume_down> cmd){... /* реакция на volume_down */}); inactive .transfer_to_state<volume_up>(active) .transfer_to_state<volume_down>(active); } Пример агента с состояниями 82 Определение реакции на вход в состояние и на выход из состояния.
  • 83. void so_define_agent() override { this >>= off; off.event([&](mhood_t<turn_on_off>){... /* включить устройство */}); on.event([&](mhood_t<turn_on_off>{... /* выключить устройство */}); active .on_enter([&]{... /* зажечь экран */}) .on_exit([&]{... /* погасить экран */}) .time_limit(15s, inactive) .event([&](mhood_t<volume_up> cmd){... /* реакция на volume_up */}) .event([&](mhood_t<volume_down> cmd){... /* реакция на volume_down */}); inactive .transfer_to_state<volume_up>(active) .transfer_to_state<volume_down>(active); } Пример агента с состояниями 83 Ограничиваем время пребывания в состоянии active. Через 15s после входа в active автоматически переводим агента в состояние inactive.
  • 84. void so_define_agent() override { this >>= off; off.event([&](mhood_t<turn_on_off>){... /* включить устройство */}); on.event([&](mhood_t<turn_on_off>{... /* выключить устройство */}); active .on_enter([&]{... /* зажечь экран */}) .on_exit([&]{... /* погасить экран */}) .time_limit(15s, inactive) .event([&](mhood_t<volume_up> cmd){... /* реакция на volume_up */}) .event([&](mhood_t<volume_down> cmd){... /* реакция на volume_down */}); inactive .transfer_to_state<volume_up>(active) .transfer_to_state<volume_down>(active); } Пример агента с состояниями 84 Делегируем обработку сообщений volume_up и volume_down состоянию active. Агент переводится в состояние active и обработчики для этих сообщений ищутся среди подписок состояния active.
  • 85. off.event([&](mhood_t<turn_on_off>) { ... /* специфический для инициализации устройства код */ this >>= on; }); on.event([&](mhood_t<turn_on_off>) { ... /* специфический для деинициализации устройства код */ this >>= off; }); Пример агента с состояниями 85
  • 86. off.event([&](mhood_t<turn_on_off>) { ... /* специфический для инициализации устройства код */ this >>= on; }); on.event([&](mhood_t<turn_on_off>) { ... /* специфический для деинициализации устройства код */ this >>= off; }); Пример агента с состояниями 86 Активными станут сразу два состояния: on и on::active. У состояния on::active будет вызван обработчик on_enter.
  • 87. off.event([&](mhood_t<turn_on_off>) { ... /* специфический для инициализации устройства код */ this >>= on; }); on.event([&](mhood_t<turn_on_off>) { ... /* специфический для деинициализации устройства код */ this >>= off; }); Пример агента с состояниями 87
  • 88. Кооперации агентов решают задачу одномоментной регистрации в SObjectizer группы взаимосвязанных агентов. Кооперации агентов 88
  • 90. Наполнение и регистрация коопераций 90 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)); (1)
  • 91. Наполнение и регистрация коопераций 91 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)); (2)
  • 92. 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)); Наполнение и регистрация коопераций 92
  • 93. Наполнение и регистрация коопераций 93 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)); (3)
  • 94. Наполнение и регистрация коопераций 94 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)); (3) Альтернативный вариант регистрации кооперации: so_5::environment_t & env = ...; // Доступ к SObjectizer. // Заставляем SObjectizer создать объект кооперации, // а затем и зарегистрировать его. env.introduce_coop("data_acquire_demo", [&](so_5::coop_t & coop) { // Наполняем кооперацию прикладными агентами. Заодно привязываем агентов к // разным диспетчерам для того, чтобы агенты работали на разных рабочих // нитях. 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(), ... ); }); // Здесь кооперация будет зарегистрирована автоматически. Шаги №1 и №3 выполняет сам SObjectizer.
  • 95. Два агента: pinger и ponger. Агент pinger отсылает сигналы ping агенту ponger. Агент ponger в ответ отсылает сигналы pong. Работают некоторое время. В конце агенты сообщают сколько сообщений получили. Классика жанра: ping-pong 95
  • 96. Первая на базе MPMC (multi-consumer) mbox-ов. Вторая на базе MPSC (single-consumer или direct) mbox-ов. Посмотрим две реализации 96
  • 97. Первая на базе MPMC (multi-consumer) mbox-ов. Вторая на базе MPSC (single-consumer или direct) mbox-ов. Посмотрим две реализации 97 Достаточно одного MPMC-mbox-а. Агент pinger подписывается на pong. Агент ponger подписывается на ping. Разные подписки. Поэтому MPMC точно знает кому и что доставлять. Практически как в случае одного MQ-брокера и двух топиков с разными именами.
  • 98. #include <so_5/all.hpp> struct ping final : public so_5::signal_t {}; struct pong final : public so_5::signal_t {}; Ping-pong через MPMC-mbox (1) 98
  • 99. class pinger final : public so_5::agent_t { public: pinger(context_t ctx, so_5::mbox_t mbox) : so_5::agent_t{std::move(ctx)}, mbox_{std::move(mbox)} {} void so_define_agent() override; void so_evt_start() override; void so_evt_finish() override; private: const so_5::mbox_t mbox_; unsigned long long pongs_{}; void on_pong(mhood_t<pong>); }; Ping-pong через MPMC-mbox (2) 99
  • 100. class pinger final : public so_5::agent_t { public: pinger(context_t ctx, so_5::mbox_t mbox) : so_5::agent_t{std::move(ctx)}, mbox_{std::move(mbox)} {} void so_define_agent() override; void so_evt_start() override; void so_evt_finish() override; private: const so_5::mbox_t mbox_; unsigned long long pongs_{}; void on_pong(mhood_t<pong>); }; Ping-pong через MPMC-mbox (2) 100
  • 101. class pinger final : public so_5::agent_t { public: pinger(context_t ctx, so_5::mbox_t mbox) : so_5::agent_t{std::move(ctx)}, mbox_{std::move(mbox)} {} void so_define_agent() override; void so_evt_start() override; void so_evt_finish() override; private: const so_5::mbox_t mbox_; unsigned long long pongs_{}; void on_pong(mhood_t<pong>); }; Ping-pong через MPMC-mbox (2) 101 Вызывается SObjectizer-ом во время регистрации кооперации для настройки агента.
  • 102. class pinger final : public so_5::agent_t { public: pinger(context_t ctx, so_5::mbox_t mbox) : so_5::agent_t{std::move(ctx)}, mbox_{std::move(mbox)} {} void so_define_agent() override; void so_evt_start() override; void so_evt_finish() override; private: const so_5::mbox_t mbox_; unsigned long long pongs_{}; void on_pong(mhood_t<pong>); }; Ping-pong через MPMC-mbox (2) 102 Первый метод, который вызывается у агента на его рабочем контексте после успешной регистрации кооперации.
  • 103. class pinger final : public so_5::agent_t { public: pinger(context_t ctx, so_5::mbox_t mbox) : so_5::agent_t{std::move(ctx)}, mbox_{std::move(mbox)} {} void so_define_agent() override; void so_evt_start() override; void so_evt_finish() override; private: const so_5::mbox_t mbox_; unsigned long long pongs_{}; void on_pong(mhood_t<pong>); }; Ping-pong через MPMC-mbox (2) 103 Последний метод, который вызывается у агента на его рабочем контексте перед тем как кооперация будет полностью дерегистрирована.
  • 104. class pinger final : public so_5::agent_t { public: pinger(context_t ctx, so_5::mbox_t mbox) : so_5::agent_t{std::move(ctx)}, mbox_{std::move(mbox)} {} void so_define_agent() override; void so_evt_start() override; void so_evt_finish() override; private: const so_5::mbox_t mbox_; unsigned long long pongs_{}; void on_pong(mhood_t<pong>); }; Ping-pong через MPMC-mbox (2) 104 Обработчик нужного нам сигнала.
  • 105. void so_define_agent() override { so_subscribe(mbox_).event(&pinger::on_pong); } void so_evt_start() override { so_5::send<ping>(mbox_); } void so_evt_finish() override { std::cout << "pongs received: " + std::to_string(pongs_) << std::endl; } void on_pong(mhood_t<pong>) { ++pongs_; so_5::send<ping>(mbox_); } Ping-pong через MPMC-mbox (3) 105
  • 106. void so_define_agent() override { so_subscribe(mbox_).event(&pinger::on_pong); } void so_evt_start() override { so_5::send<ping>(mbox_); } void so_evt_finish() override { std::cout << "pongs received: " + std::to_string(pongs_) << std::endl; } void on_pong(mhood_t<pong>) { ++pongs_; so_5::send<ping>(mbox_); } Ping-pong через MPMC-mbox (3) 106 Подписка на интересующий нас сигнал из заданного mbox-а.
  • 107. void so_define_agent() override { so_subscribe(mbox_).event(&pinger::on_pong); } void so_evt_start() override { so_5::send<ping>(mbox_); } void so_evt_finish() override { std::cout << "pongs received: " + std::to_string(pongs_) << std::endl; } void on_pong(mhood_t<pong>) { ++pongs_; so_5::send<ping>(mbox_); } Ping-pong через MPMC-mbox (3) 107 Сразу отсылаем первый ping как только начали работать.
  • 108. void so_define_agent() override { so_subscribe(mbox_).event(&pinger::on_pong); } void so_evt_start() override { so_5::send<ping>(mbox_); } void so_evt_finish() override { std::cout << "pongs received: " + std::to_string(pongs_) << std::endl; } void on_pong(mhood_t<pong>) { ++pongs_; so_5::send<ping>(mbox_); } Ping-pong через MPMC-mbox (3) 108 В самом конце печатаем результаты.
  • 109. void so_define_agent() override { so_subscribe(mbox_).event(&pinger::on_pong); } void so_evt_start() override { so_5::send<ping>(mbox_); } void so_evt_finish() override { std::cout << "pongs received: " + std::to_string(pongs_) << std::endl; } void on_pong(mhood_t<pong>) { ++pongs_; so_5::send<ping>(mbox_); } Ping-pong через MPMC-mbox (3) 109 Реакция на ответный pong: - обновляем статистику; - отсылаем очередной ping.
  • 110. class ponger final : public so_5::agent_t { public: ponger(context_t ctx, so_5::mbox_t mbox) : so_5::agent_t{std::move(ctx)}, mbox_{std::move(mbox)} { } void so_define_agent() override; void so_evt_finish() override; private: const so_5::mbox_t mbox_; unsigned long long pings_{}; void on_ping(mhood_t<ping>); }; Ping-pong через MPMC-mbox (4) 110
  • 111. class ponger final : public so_5::agent_t { public: ponger(context_t ctx, so_5::mbox_t mbox) : so_5::agent_t{std::move(ctx)}, mbox_{std::move(mbox)} { } void so_define_agent() override; void so_evt_finish() override; private: const so_5::mbox_t mbox_; unsigned long long pings_{}; void on_ping(mhood_t<ping>); }; Ping-pong через MPMC-mbox (4) 111 В отличии от pinger-а, у ponger-а нет метода so_evt_start. Это потому, что ponger-у ничего не нужно делать самому, он будет отвечать на сигналы от pinger-а.
  • 112. void so_define_agent() override { so_subscribe(mbox_).event(&ponger::on_ping); } void so_evt_finish() override { std::cout << "pings received: " + std::to_string(pings_) << std::endl; } void on_ping(mhood_t<ping>) { ++pings_; so_5::send<pong>(mbox_); } Ping-pong через MPMC-mbox (5) 112
  • 113. int main() { so_5::launch([](so_5::environment_t & env) { env.introduce_coop("pinger-ponger", so_5::disp::active_obj::create_private_disp(env)->binder(), [&](so_5::coop_t & coop) { const auto mbox = env.create_mbox(); coop.make_agent<pinger>(mbox); coop.make_agent<ponger>(mbox); } ); std::this_thread::sleep_for(std::chrono::seconds{1}); env.stop(); }); } Ping-pong через MPMC-mbox (6) 113
  • 114. int main() { so_5::launch([](so_5::environment_t & env) { env.introduce_coop("pinger-ponger", so_5::disp::active_obj::create_private_disp(env)->binder(), [&](so_5::coop_t & coop) { const auto mbox = env.create_mbox(); coop.make_agent<pinger>(mbox); coop.make_agent<ponger>(mbox); } ); std::this_thread::sleep_for(std::chrono::seconds{1}); env.stop(); }); } Ping-pong через MPMC-mbox (6) 114 Запускает SObjectizer. Возвращает управление после того, как SObjectizer будет остановлен.
  • 115. int main() { so_5::launch([](so_5::environment_t & env) { env.introduce_coop("pinger-ponger", so_5::disp::active_obj::create_private_disp(env)->binder(), [&](so_5::coop_t & coop) { const auto mbox = env.create_mbox(); coop.make_agent<pinger>(mbox); coop.make_agent<ponger>(mbox); } ); std::this_thread::sleep_for(std::chrono::seconds{1}); env.stop(); }); } Ping-pong через MPMC-mbox (6) 115 Действия, которые должны быть выполнены сразу после запуска SObjectizer.
  • 116. int main() { so_5::launch([](so_5::environment_t & env) { env.introduce_coop("pinger-ponger", so_5::disp::active_obj::create_private_disp(env)->binder(), [&](so_5::coop_t & coop) { const auto mbox = env.create_mbox(); coop.make_agent<pinger>(mbox); coop.make_agent<ponger>(mbox); } ); std::this_thread::sleep_for(std::chrono::seconds{1}); env.stop(); }); } Ping-pong через MPMC-mbox (6) 116 Диспетчер active_obj выделит отдельную рабочую нить каждому агенту кооперации.
  • 117. int main() { so_5::launch([](so_5::environment_t & env) { env.introduce_coop("pinger-ponger", so_5::disp::active_obj::create_private_disp(env)->binder(), [&](so_5::coop_t & coop) { const auto mbox = env.create_mbox(); coop.make_agent<pinger>(mbox); coop.make_agent<ponger>(mbox); } ); std::this_thread::sleep_for(std::chrono::seconds{1}); env.stop(); }); } Ping-pong через MPMC-mbox (6) 117 Создаем mbox для обмена сообщениями между агентами.
  • 118. int main() { so_5::launch([](so_5::environment_t & env) { env.introduce_coop("pinger-ponger", so_5::disp::active_obj::create_private_disp(env)->binder(), [&](so_5::coop_t & coop) { const auto mbox = env.create_mbox(); coop.make_agent<pinger>(mbox); coop.make_agent<ponger>(mbox); } ); std::this_thread::sleep_for(std::chrono::seconds{1}); env.stop(); }); } Ping-pong через MPMC-mbox (6) 118 Наполняем кооперацию агентами.
  • 119. int main() { so_5::launch([](so_5::environment_t & env) { env.introduce_coop("pinger-ponger", so_5::disp::active_obj::create_private_disp(env)->binder(), [&](so_5::coop_t & coop) { const auto mbox = env.create_mbox(); coop.make_agent<pinger>(mbox); coop.make_agent<ponger>(mbox); } ); std::this_thread::sleep_for(std::chrono::seconds{1}); env.stop(); }); } Ping-pong через MPMC-mbox (6) 119 Даем одну секунду на работу и останавливаем SObjectizer.
  • 120. pongs received: 463979 pings received: 463980 Ping-pong через MPMC-mbox. Результат 120 Ubuntu 16.04 LTS, x86_64 Intel(R) Core(TM) i7-3667U CPU @ 2.00GHz GCC 5.5.0
  • 121. class pinger final : public so_5::agent_t { public: using so_5::agent_t::agent_t; void set_ponger_mbox(so_5::mbox_t mbox) { ponger_ = std::move(mbox); } void so_define_agent() override; void so_evt_start() override; void so_evt_finish() override; private: so_5::mbox_t ponger_; unsigned long long pongs_{}; void on_pong(mhood_t<pong>); }; Ping-pong через MPSC-mbox (1) 121
  • 122. class pinger final : public so_5::agent_t { public: using so_5::agent_t::agent_t; void set_ponger_mbox(so_5::mbox_t mbox) { ponger_ = std::move(mbox); } void so_define_agent() override; void so_evt_start() override; void so_evt_finish() override; private: so_5::mbox_t ponger_; unsigned long long pongs_{}; void on_pong(mhood_t<pong>); }; Ping-pong через MPSC-mbox (1) 122 Собственный конструктор уже не нужен. Наследуем конструкторы из базового класса.
  • 123. class pinger final : public so_5::agent_t { public: using so_5::agent_t::agent_t; void set_ponger_mbox(so_5::mbox_t mbox) { ponger_ = std::move(mbox); } void so_define_agent() override; void so_evt_start() override; void so_evt_finish() override; private: so_5::mbox_t ponger_; unsigned long long pongs_{}; void on_pong(mhood_t<pong>); }; Ping-pong через MPSC-mbox (1) 123
  • 124. void pinger::so_define_agent() { so_subscribe_self().event(&pinger::on_pong); } void pinger::so_evt_start() { so_5::send<ping>(ponger_); } void pinger::so_evt_finish() { std::cout << "pongs received: " + std::to_string(pongs_) << std::endl; } void pinger::on_pong(mhood_t<pong>) { ++pongs_; so_5::send<ping>(ponger_); } Ping-pong через MPSC-mbox (2) 124
  • 125. void pinger::so_define_agent() { so_subscribe_self().event(&pinger::on_pong); } void pinger::so_evt_start() { so_5::send<ping>(ponger_); } void pinger::so_evt_finish() { std::cout << "pongs received: " + std::to_string(pongs_) << std::endl; } void pinger::on_pong(mhood_t<pong>) { ++pongs_; so_5::send<ping>(ponger_); } Ping-pong через MPSC-mbox (2) 125 Теперь мы ждем сигналы pong из персонального direct mbox-а.
  • 126. class ponger final : public so_5::agent_t { public: using so_5::agent_t::agent_t; void set_pinger_mbox(so_5::mbox_t mbox) { pinger_ = std::move(mbox); } void so_define_agent() override; void so_evt_finish() override; private: so_5::mbox_t pinger_; unsigned long long pings_{}; void on_ping(mhood_t<ping>); }; Ping-pong через MPSC-mbox (3) 126
  • 127. class ponger final : public so_5::agent_t { public: using so_5::agent_t::agent_t; void set_pinger_mbox(so_5::mbox_t mbox) { pinger_ = std::move(mbox); } void so_define_agent() override; void so_evt_finish() override; private: so_5::mbox_t pinger_; unsigned long long pings_{}; void on_ping(mhood_t<ping>); }; Ping-pong через MPSC-mbox (3) 127
  • 128. void ponger::so_define_agent() { so_subscribe_self().event(&ponger::on_ping); } void ponger::so_evt_finish() { std::cout << "pings received: " + std::to_string(pings_) << std::endl; } void ponger::on_ping(mhood_t<ping>) { ++pings_; so_5::send<pong>(pinger_); } Ping-pong через MPSC-mbox (4) 128
  • 129. void ponger::so_define_agent() { so_subscribe_self().event(&ponger::on_ping); } void ponger::so_evt_finish() { std::cout << "pings received: " + std::to_string(pings_) << std::endl; } void ponger::on_ping(mhood_t<ping>) { ++pings_; so_5::send<pong>(pinger_); } Ping-pong через MPSC-mbox (4) 129
  • 130. int main() { so_5::launch([](so_5::environment_t & env) { env.introduce_coop("pinger-ponger", so_5::disp::active_obj::create_private_disp(env)->binder(), [](so_5::coop_t & coop) { auto a_pinger = coop.make_agent<pinger>(); auto a_ponger = coop.make_agent<ponger>(); a_pinger->set_ponger_mbox(a_ponger->so_direct_mbox()); a_ponger->set_pinger_mbox(a_pinger->so_direct_mbox()); } ); std::this_thread::sleep_for(std::chrono::seconds{1}); env.stop(); }); } Ping-pong через MPSC-mbox (5) 130
  • 131. int main() { so_5::launch([](so_5::environment_t & env) { env.introduce_coop("pinger-ponger", so_5::disp::active_obj::create_private_disp(env)->binder(), [](so_5::coop_t & coop) { auto a_pinger = coop.make_agent<pinger>(); auto a_ponger = coop.make_agent<ponger>(); a_pinger->set_ponger_mbox(a_ponger->so_direct_mbox()); a_ponger->set_pinger_mbox(a_pinger->so_direct_mbox()); } ); std::this_thread::sleep_for(std::chrono::seconds{1}); env.stop(); }); } Ping-pong через MPSC-mbox (5) 131 Сперва нужно создать агентов...
  • 132. int main() { so_5::launch([](so_5::environment_t & env) { env.introduce_coop("pinger-ponger", so_5::disp::active_obj::create_private_disp(env)->binder(), [](so_5::coop_t & coop) { auto a_pinger = coop.make_agent<pinger>(); auto a_ponger = coop.make_agent<ponger>(); a_pinger->set_ponger_mbox(a_ponger->so_direct_mbox()); a_ponger->set_pinger_mbox(a_pinger->so_direct_mbox()); } ); std::this_thread::sleep_for(std::chrono::seconds{1}); env.stop(); }); } Ping-pong через MPSC-mbox (5) 132 ...и только затем их можно будет связать друг с другом.
  • 133. pongs received: 594540 pings received: 594540 Ping-pong через MPSC-mbox. Результат 133 Ubuntu 16.04 LTS, x86_64 Intel(R) Core(TM) i7-3667U CPU @ 2.00GHz GCC 5.5.0
  • 134. pongs received: 594540 pings received: 594540 Ping-pong через MPSC-mbox. Результат 134 Ubuntu 16.04 LTS, x86_64 Intel(R) Core(TM) i7-3667U CPU @ 2.00GHz GCC 5.5.0 В случае MPMC было ~460K сообщений в одну сторону. В случае MPSC стало ~600K сообщений. Откуда такой разрыв?
  • 135. pongs received: 594540 pings received: 594540 Ping-pong через MPSC-mbox. Результат 135 Ubuntu 16.04 LTS, x86_64 Intel(R) Core(TM) i7-3667U CPU @ 2.00GHz GCC 5.5.0 В случае MPMC было ~460K сообщений в одну сторону. В случае MPSC стало ~600K сообщений. Откуда такой разрыв? В случае с MPMC-mbox-ами есть накладные расходы на поиск подписок в самом mbox-е. В случае с MPSC-mbox-ами таких накладных расходов нет.
  • 136. А что, если поместить pinger-а и ponger-а на одну общую нить? *_one_thread 136
  • 137. int main() { so_5::launch([](so_5::environment_t & env) { env.introduce_coop("pinger-ponger", so_5::disp::active_obj::create_private_disp(env)->binder(), so_5::disp::one_thread::create_private_disp(env)->binder(), [](so_5::coop_t & coop) { auto a_pinger = coop.make_agent<pinger>(); auto a_ponger = coop.make_agent<ponger>(); a_pinger->set_ponger_mbox(a_ponger->so_direct_mbox()); a_ponger->set_pinger_mbox(a_pinger->so_direct_mbox()); } ); std::this_thread::sleep_for(std::chrono::seconds{1}); env.stop(); }); } *_one_thread 137
  • 138. pongs received: 2486948 pings received: 2486949 *_one_thread. Результат 138 Ubuntu 16.04 LTS, x86_64 Intel(R) Core(TM) i7-3667U CPU @ 2.00GHz GCC 5.5.0
  • 139. pongs received: 2486948 pings received: 2486949 *_one_thread. Результат 139 Ubuntu 16.04 LTS, x86_64 Intel(R) Core(TM) i7-3667U CPU @ 2.00GHz GCC 5.5.0 ~2.5M сообщений в одну сторону вместо ~600K. Почему так?
  • 140. Когда pinger и ponger работают на разных рабочих нитях: *_one_thread. Пояснение 140 pinger ponger
  • 141. Когда pinger и ponger работают на разных рабочих нитях: *_one_thread. Пояснение 141 pinger ponger Моменты, когда входящая очередь пуста и нить вынуждена ждать на пустой очереди.
  • 143. Простой Web-сервис, который масштабирует и перекодирует картинки. https://bitbucket.org/sobjectizerteam/shrimp-demo https://github.com/Stiffstream/shrimp-demo https://habr.com/post/416387/ https://habr.com/post/417527/ https://habr.com/post/420353/ Демо-проект Shrimp (1) 143
  • 144. SObjectizer используется для распараллеливания обработки запросов. Агенты двух типов: a_transform_manager_t a_transformer_t Демо-проект Shrimp (2) 144
  • 147. void a_transform_manager_t::so_define_agent() { so_subscribe_self() .event( &a_transform_manager_t::on_resize_request ) .event( &a_transform_manager_t::on_resize_result ) .event( &a_transform_manager_t::on_delete_cache_request ) .event( &a_transform_manager_t::on_negative_delete_cache_response ) .event( &a_transform_manager_t::on_clear_cache ) .event( &a_transform_manager_t::on_check_pending_requests ); } Демо-проект Shrimp (5) 147
  • 148. void a_transform_manager_t::so_define_agent() { so_subscribe_self() .event( &a_transform_manager_t::on_resize_request ) .event( &a_transform_manager_t::on_resize_result ) .event( &a_transform_manager_t::on_delete_cache_request ) .event( &a_transform_manager_t::on_negative_delete_cache_response ) .event( &a_transform_manager_t::on_clear_cache ) .event( &a_transform_manager_t::on_check_pending_requests ); } Демо-проект Shrimp (5) 148 Изначально этой функциональности даже не предполагалось. Она появилась в результате работы над Shrimp-ом.
  • 149. Итоги? Что можно сказать после 16-ти лет в обнимку с SObjectizer? 149
  • 150. Которые уже произносились неоднократно. На C++Russia 2017: Традиционные банальности 150
  • 151. Не для всех задач Модель Акторов применима. Но там, где применима удобный фреймворк рулит и бибикает. Тем более, что SObjectizer ‒ это не только Actor Model, но и Pub-Sub, и CSP. К хорошему быстро привыкаешь 151
  • 152. Модель акторов имеет смысл использовать и в C++. Нет надобности велосипедить. Есть из чего выбирать. Название правильного фреймворка начинается на SObjec... ;) Итого: 152
  • 153. SObjectizer: https://stiffstream.com/ru/products/sobjectizer.html Документация по SObjectizer: https://sourceforge.net/p/sobjectizer/wiki/Home/ Статьи о SObjectizer на русском: https://habr.com/users/eao197/posts Серия презентаций о SObjectizer на английском "Dive into SObjectizer-5.5": Intro, Agent's States, More About Coops, Exceptions, Timers, Synchonous Interaction, Message Limits, Dispatchers, Message Chains, Mutable Messages. Если вам нужна помощь по SObjectizer: https://stiffstream.com 153