Successfully reported this slideshow.
We use your LinkedIn profile and activity data to personalize ads and to show you more relevant ads. You can change your ad preferences anytime.
St.Petersburg C++ User Group
Акторы в C++:
взгляд старого практикующего
актородела
Евгений Охотников
Коротко о себе
Профессиональный разработчик с 1994-го.
● 1994-2000: АСУ ТП/SCADA и т.п.;
● 2001-2014: телеком, платежные с...
Начнем издалека
Модель Акторов
на пальцах
3
Многопоточность – это...
инструмент, который активно применяется в двух очень разных
областях:
● parallel computing;
● con...
Многопоточность без ошибок?
Фантастика.
Даже имея 20 лет опыта.
Но почему?
5
Изменяемое разделяемое состояние
Главный фактор сложности в многопоточном программировании.
6
Убрать разделяемое состояние
Ничего не разделяем. Ни за что не боремся.
Пусть каждый поток владеет своими собственными дан...
Потокам нужно общаться, но как?
Кажется, что есть два способа:
1. Синхронно.
2. Асинхронно.
Но это только кажется...
Синхр...
Асинхронный обмен сообщениями
9
X Y Z
Просто же!
Есть потоки. У каждого есть очередь входящих сообщений.
Поток спит, если очередь входящих сообщений пуста.
Пото...
Дополнительный бонус
Если сообщения несут копию данных, то мы получаем очень
приятный бонус:
прозрачный переход к распреде...
Модель Акторов. Основные принципы
● актор – это некая сущность, обладающая поведением;
● акторы реагируют на входящие сооб...
Актор – это некоторая сущность
Не более того.
Актором может быть отдельный процесс. Например, Erlang.
Актором может быть о...
Модель Акторов (официоз №1)
Появилась в 1973-ем году благодаря работам Карла Хьюитта (Carl
Hewitt).
Была затем развита в 1...
Модель Акторов (официоз №2)
Отправные точки для желающих погрузиться в формальную
теорию Модели Акторов:
https://en.wikipe...
Вопрос ребром!
Почему Модель Акторов
привлекает внимание?
16
Чаще всего упоминается:
1. Простота. Нет разделяемых данных. Асинхронный обмен
сообщениями.
2. Аналогии с реальным миром. ...
Модель Акторов как...
...способ смотреть на мир. Другими словами: если у вас в руках
молоток...
18
Вопрос на миллион
Модель Акторов ‒ это
серебряная пуля?
19
Границы применимости
Есть несколько маркеров, которые могут подсказать, что выбор
Модели Акторов будет оправданным.
Могут ...
Маркер №1
Fire-and-Forget
21
Fire-and-Forget
1. Тотальный контроль не нужен.
2. Результаты начатых операций вот прямо здесь и сейчас не
нужны.
3. Если ...
Fire-and-Forget: примеры "за" и "против"
Пример №1: две рабочие нити, одна только читает данные, вторая
только разбирает и...
Маркер №2
Конечные автоматы
24
Принципы Модели Акторов еще раз
● актор – это некая сущность, обладающая поведением;
● акторы реагируют на входящие сообще...
О каких конечных автоматах речь?
26
Маркер №3
Архитектура Shared Nothing
27
Shared Nothing: контрольный в голову
Могут ли сущности в программе работать без каких либо
разделяемых данных?
Может ли ка...
Не маркер, но тем не менее
Таймеры
29
Напрямую не связано с Моделью Акторов, но...
...таймеры в виде отложенных сообщений ‒ это очень удобно на
практике.
Следст...
Общие слова закончились...
...надеюсь, что пока было понятно
31
Ближе к телу!
Модель Акторов и C++:
миф или реальность?
32
Реальность!
● промышленная автоматизация (АСУ ТП);
● телеком;
● электронная и мобильная коммерция;
● имитационное моделиро...
Есть несколько нюансов
Вызванных спецификой C++:
● небезопасностью;
● специфическими прикладными нишами (embedded,
high-pe...
Есть готовые инструменты:
Наиболее известные:
● QP/C++ (двойная лицензия);
● Just::Thread Pro: Actors Edition (коммерческа...
А теперь о главном
SObjectizer
36
Давным-давно, в далекой-далекой...
1994-й год.
Гомель. КБ Системного Программирования.
Отдел АСУТП.
Попытка сделать объект...
Объектная SCADA. Итог
SCADA Objectizer.
1998-й год.
Опробован в реальном проекте.
Прекратил свое существование в начале 20...
Предпосылки к перерождению
Начало 2002-го.
Компания "Интервэйл".
Двое участников разработки SCADA Objectizer.
Небольшая GU...
Апрель 2002-го года.
SObjectizer = SCADA + Objectizer.
SObjectizer-4 (в четвертый раз все сначала).
Четвертое пришествие
40
Май 2002-го: начало использования SO-4 в разработке.
Март 2006-го: перевод SO-4 в категорию OpenSource проектов под
BSD-ли...
В компании "Интервэйл":
● SMS/USSD-агрегатор;
● куски платежных сервисов, платформ электронной коммерции;
● система монито...
SObjectizer v.5.5.22.1:
● май 2018;
● ~31 KLOC кода;
● ~35 KLOC тестов;
● ~10 KLOC примеров (работающих).
С++11. Минимальн...
...мы не делали реализацию Модели Акторов.
На самом деле...
44
...упростить для себя разработку многопоточного кода.
На момент начала работ над SObjectizer мы даже не знали про
Модель А...
Хватит общих слов!
Что мы получили в SObjectizer-5?
46
Агенты. Кооперации агентов. (Агент == Актор и наоборот)
Сообщения. Подписки на сообщения.
Почтовые ящики.
Диспетчеры.
Из ч...
Все взаимодействие между агентами в SObjectizer идет только
через асинхронные сообщения.
Сообщения это объекты.
Тип сообще...
struct request : public so_5::message_t
{
std::int64_t id_;
std::map<std::string, std::string> params_;
std::vector<std::u...
struct request : public so_5::message_t
{
std::int64_t id_;
std::map<std::string, std::string> params_;
std::vector<std::u...
struct request : public so_5::message_t
{
std::int64_t id_;
std::map<std::string, std::string> params_;
std::vector<std::u...
struct request : public so_5::message_t
{
std::int64_t id_;
std::map<std::string, std::string> params_;
std::vector<std::u...
struct get_status : public so_5::signal_t {};
Еще есть сигналы
53
// Безотлагательная отсылка сообщения.
so_5::send<request>(target,
// Все это через perfect-forwarding идет в конструктор ...
// Безотлагательная отсылка сообщения.
so_5::send<request>(target,
// Все это через perfect-forwarding идет в конструктор ...
В "традиционной" Модели Акторов адресатом сообщения
является актор.
В SObjectizer сообщения отсылаются в "почтовый ящик".
...
Подписка на сообщение из mbox-а
57
MPMC (Multi-Producer/Multi-Consumer)
любой может послать,
любой может подписаться.
MPSC (Multi-Producer/Single-Consumer)
л...
MPMC (Multi-Producer/Multi-Consumer)
любой может послать,
любой может подписаться.
MPSC (Multi-Producer/Single-Consumer)
л...
MPMC (Multi-Producer/Multi-Consumer)
любой может послать,
любой может подписаться.
MPSC (Multi-Producer/Single-Consumer)
л...
// Подписка на сообщение из mbox-а.
so_subscribe(some_mbox).event(... /* обработчик */);
// Подписка на сообщение из собст...
// Подписка на сообщение из mbox-а.
so_subscribe(some_mbox).event(... /* обработчик */);
// Подписка на сообщение из собст...
Именно диспетчер определяет где и когда агент будет
обрабатывать свои сообщения.
Может быть запущено сразу несколько диспе...
В SObjectizer есть диспетчеры
64
active_group
active_obj
adv_thread_pool
one_thread
prio_dedicated_threads::one_per_prio
prio_one_thread::quoted_round_robi...
Зачем так много?
66
Зачем так много?
67
Иногда может потребоваться сделать диспетчер под задачу:
https://habr.com/post/353712/
Как правило, реализуются как объекты классов, унаследованных
от so_5::agent_t.
class hello_world final : public so_5::agen...
Как правило, реализуются как объекты классов, унаследованных
от so_5::agent_t.
class hello_world final : public so_5::agen...
Состояния позволяют реализовывать сложные иерархические
конечные автоматы:
● вложенные состояния;
● on_enter/on_exit для с...
Состояния позволяют реализовывать сложные иерархические
конечные автоматы:
● вложенные состояния;
● on_enter/on_exit для с...
Пример агента с состояниями
72
class player_demo final : public so_5::agent_t {
state_t off{this, "off"},
on{this, "on"},
active{initial_substate_of{on},...
class player_demo final : public so_5::agent_t {
state_t off{this, "off"},
on{this, "on"},
active{initial_substate_of{on},...
class player_demo final : public so_5::agent_t {
state_t off{this, "off"},
on{this, "on"},
active{initial_substate_of{on},...
class player_demo final : public so_5::agent_t {
state_t off{this, "off"},
on{this, "on"},
active{initial_substate_of{on},...
void so_define_agent() override {
this >>= off;
off.event([&](mhood_t<turn_on_off>){... /* включить устройство */});
on.ev...
void so_define_agent() override {
this >>= off;
off.event([&](mhood_t<turn_on_off>){... /* включить устройство */});
on.ev...
void so_define_agent() override {
this >>= off;
off.event([&](mhood_t<turn_on_off>){... /* включить устройство */});
on.ev...
void so_define_agent() override {
this >>= off;
off.event([&](mhood_t<turn_on_off>){... /* включить устройство */});
on.ev...
void so_define_agent() override {
this >>= off;
off.event([&](mhood_t<turn_on_off>){... /* включить устройство */});
on.ev...
void so_define_agent() override {
this >>= off;
off.event([&](mhood_t<turn_on_off>){... /* включить устройство */});
on.ev...
void so_define_agent() override {
this >>= off;
off.event([&](mhood_t<turn_on_off>){... /* включить устройство */});
on.ev...
void so_define_agent() override {
this >>= off;
off.event([&](mhood_t<turn_on_off>){... /* включить устройство */});
on.ev...
off.event([&](mhood_t<turn_on_off>) {
... /* специфический для инициализации устройства код */
this >>= on;
});
on.event([...
off.event([&](mhood_t<turn_on_off>) {
... /* специфический для инициализации устройства код */
this >>= on;
});
on.event([...
off.event([&](mhood_t<turn_on_off>) {
... /* специфический для инициализации устройства код */
this >>= on;
});
on.event([...
Кооперации агентов решают задачу одномоментной регистрации в
SObjectizer группы взаимосвязанных агентов.
Кооперации агенто...
Кооперации агентов
89
Наполнение и регистрация коопераций
90
so_5::environment_t & env = ...; // Доступ к SObjectizer.
// Заставляем SObjectizer...
Наполнение и регистрация коопераций
91
so_5::environment_t & env = ...; // Доступ к SObjectizer.
// Заставляем SObjectizer...
so_5::environment_t & env = ...; // Доступ к SObjectizer.
// Заставляем SObjectizer создать объект кооперации.
auto coop =...
Наполнение и регистрация коопераций
93
so_5::environment_t & env = ...; // Доступ к SObjectizer.
// Заставляем SObjectizer...
Наполнение и регистрация коопераций
94
so_5::environment_t & env = ...; // Доступ к SObjectizer.
// Заставляем SObjectizer...
Два агента: pinger и ponger.
Агент pinger отсылает сигналы ping агенту ponger.
Агент ponger в ответ отсылает сигналы pong....
Первая на базе MPMC (multi-consumer) mbox-ов.
Вторая на базе MPSC (single-consumer или direct) mbox-ов.
Посмотрим две реал...
Первая на базе MPMC (multi-consumer) mbox-ов.
Вторая на базе MPSC (single-consumer или direct) mbox-ов.
Посмотрим две реал...
#include <so_5/all.hpp>
struct ping final : public so_5::signal_t {};
struct pong final : public so_5::signal_t {};
Ping-p...
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...
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...
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...
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...
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...
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...
void so_define_agent() override {
so_subscribe(mbox_).event(&pinger::on_pong);
}
void so_evt_start() override {
so_5::send...
void so_define_agent() override {
so_subscribe(mbox_).event(&pinger::on_pong);
}
void so_evt_start() override {
so_5::send...
void so_define_agent() override {
so_subscribe(mbox_).event(&pinger::on_pong);
}
void so_evt_start() override {
so_5::send...
void so_define_agent() override {
so_subscribe(mbox_).event(&pinger::on_pong);
}
void so_evt_start() override {
so_5::send...
void so_define_agent() override {
so_subscribe(mbox_).event(&pinger::on_pong);
}
void so_evt_start() override {
so_5::send...
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...
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...
void so_define_agent() override {
so_subscribe(mbox_).event(&ponger::on_ping);
}
void so_evt_finish() override {
std::cout...
int main() {
so_5::launch([](so_5::environment_t & env) {
env.introduce_coop("pinger-ponger",
so_5::disp::active_obj::crea...
int main() {
so_5::launch([](so_5::environment_t & env) {
env.introduce_coop("pinger-ponger",
so_5::disp::active_obj::crea...
int main() {
so_5::launch([](so_5::environment_t & env) {
env.introduce_coop("pinger-ponger",
so_5::disp::active_obj::crea...
int main() {
so_5::launch([](so_5::environment_t & env) {
env.introduce_coop("pinger-ponger",
so_5::disp::active_obj::crea...
int main() {
so_5::launch([](so_5::environment_t & env) {
env.introduce_coop("pinger-ponger",
so_5::disp::active_obj::crea...
int main() {
so_5::launch([](so_5::environment_t & env) {
env.introduce_coop("pinger-ponger",
so_5::disp::active_obj::crea...
int main() {
so_5::launch([](so_5::environment_t & env) {
env.introduce_coop("pinger-ponger",
so_5::disp::active_obj::crea...
pongs received: 463979
pings received: 463980
Ping-pong через MPMC-mbox. Результат
120
Ubuntu 16.04 LTS, x86_64
Intel(R) C...
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) ...
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) ...
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) ...
void pinger::so_define_agent() {
so_subscribe_self().event(&pinger::on_pong);
}
void pinger::so_evt_start() {
so_5::send<p...
void pinger::so_define_agent() {
so_subscribe_self().event(&pinger::on_pong);
}
void pinger::so_evt_start() {
so_5::send<p...
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) ...
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) ...
void ponger::so_define_agent() {
so_subscribe_self().event(&ponger::on_ping);
}
void ponger::so_evt_finish() {
std::cout <...
void ponger::so_define_agent() {
so_subscribe_self().event(&ponger::on_ping);
}
void ponger::so_evt_finish() {
std::cout <...
int main() {
so_5::launch([](so_5::environment_t & env) {
env.introduce_coop("pinger-ponger",
so_5::disp::active_obj::crea...
int main() {
so_5::launch([](so_5::environment_t & env) {
env.introduce_coop("pinger-ponger",
so_5::disp::active_obj::crea...
int main() {
so_5::launch([](so_5::environment_t & env) {
env.introduce_coop("pinger-ponger",
so_5::disp::active_obj::crea...
pongs received: 594540
pings received: 594540
Ping-pong через MPSC-mbox. Результат
133
Ubuntu 16.04 LTS, x86_64
Intel(R) C...
pongs received: 594540
pings received: 594540
Ping-pong через MPSC-mbox. Результат
134
Ubuntu 16.04 LTS, x86_64
Intel(R) C...
pongs received: 594540
pings received: 594540
Ping-pong через MPSC-mbox. Результат
135
Ubuntu 16.04 LTS, x86_64
Intel(R) C...
А что, если поместить 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::crea...
pongs received: 2486948
pings received: 2486949
*_one_thread. Результат
138
Ubuntu 16.04 LTS, x86_64
Intel(R) Core(TM) i7-...
pongs received: 2486948
pings received: 2486949
*_one_thread. Результат
139
Ubuntu 16.04 LTS, x86_64
Intel(R) Core(TM) i7-...
Когда pinger и ponger работают на разных рабочих нитях:
*_one_thread. Пояснение
140
pinger ponger
Когда pinger и ponger работают на разных рабочих нитях:
*_one_thread. Пояснение
141
pinger ponger
Моменты, когда входящая ...
Хватит синтетики!
Более-менее реальный пример
142
Простой Web-сервис, который масштабирует и перекодирует
картинки.
https://bitbucket.org/sobjectizerteam/shrimp-demo
https:...
SObjectizer используется для распараллеливания обработки
запросов.
Агенты двух типов:
a_transform_manager_t
a_transformer_...
Демо-проект Shrimp (3)
145
void a_transformer_t::so_define_agent()
{
so_subscribe_self().event(
&a_transformer_t::on_resize_request );
}
Демо-проект ...
void a_transform_manager_t::so_define_agent()
{
so_subscribe_self()
.event( &a_transform_manager_t::on_resize_request )
.e...
void a_transform_manager_t::so_define_agent()
{
so_subscribe_self()
.event( &a_transform_manager_t::on_resize_request )
.e...
Итоги?
Что можно сказать после 16-ти лет
в обнимку с SObjectizer?
149
Которые уже произносились неоднократно. На C++Russia 2017:
Традиционные банальности
150
Не для всех задач Модель Акторов применима.
Но там, где применима удобный фреймворк рулит и бибикает.
Тем более, что SObje...
Модель акторов имеет смысл использовать и в C++.
Нет надобности велосипедить. Есть из чего выбирать.
Название правильного ...
SObjectizer: https://stiffstream.com/ru/products/sobjectizer.html
Документация по SObjectizer: https://sourceforge.net/p/s...
Upcoming SlideShare
Loading in …5
×

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

93 views

Published on

Автор доклада более 16 лет отвечает за развитие Open-Source фреймворка SObjectizer -- одного из немногих живых, эволюционирующих, кросс-платформенных фреймворков для C++, базирующихся на Модели Акторов. При этом SObjectizer никогда не был исследовательским экспериментом и с самого начала использовался в ряде business-critical проектов.

За годы разработки и эксплуатации SObjectizer накопился некоторый практический опыт использования акторов в С++, которым докладчик поделится со слушателями. Речь пойдет о том, почему Модель Акторов выглядит привлекательной, где и когда ее выгодно использовать. Какие особенности накладывает именно С++ и разумно ли использовать Модель Акторов в C++? Почему реализации Модели Акторов для C++ настолько разные и почему SObjectizer получился именно таким?

Published in: Software
  • Be the first to comment

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

  1. 1. St.Petersburg C++ User Group Акторы в C++: взгляд старого практикующего актородела Евгений Охотников
  2. 2. Коротко о себе Профессиональный разработчик с 1994-го. ● 1994-2000: АСУ ТП/SCADA и т.п.; ● 2001-2014: телеком, платежные сервисы; ● 2014-...: OpenSource-инструментарий для C++ "Тот самый eao197" (с) RSDN Автор блога "Размышлизмы eao197" (http://eao197.blogspot.com/) Сооснователь stiffstream.com 2
  3. 3. Начнем издалека Модель Акторов на пальцах 3
  4. 4. Многопоточность – это... инструмент, который активно применяется в двух очень разных областях: ● parallel computing; ● concurrent computing. Concurrent computing может быть и без многопоточности. Будем говорить о concurrent computing. 4
  5. 5. Многопоточность без ошибок? Фантастика. Даже имея 20 лет опыта. Но почему? 5
  6. 6. Изменяемое разделяемое состояние Главный фактор сложности в многопоточном программировании. 6
  7. 7. Убрать разделяемое состояние Ничего не разделяем. Ни за что не боремся. Пусть каждый поток владеет своими собственными данными. И никто кроме потока-владельца не имеет к этим данным доступа. Принцип shared nothing (широко известен в узких кругах). https://en.wikipedia.org/wiki/Shared_nothing_architecture 7
  8. 8. Потокам нужно общаться, но как? Кажется, что есть два способа: 1. Синхронно. 2. Асинхронно. Но это только кажется... Синхронное взаимодействие – это путь в никуда. 8
  9. 9. Асинхронный обмен сообщениями 9 X Y Z
  10. 10. Просто же! Есть потоки. У каждого есть очередь входящих сообщений. Поток спит, если очередь входящих сообщений пуста. Поток просыпается, когда для него появляются входящие сообщения. Потоки общаются только через асинхронные сообщения. 10
  11. 11. Дополнительный бонус Если сообщения несут копию данных, то мы получаем очень приятный бонус: прозрачный переход к распределенности. 11
  12. 12. Модель Акторов. Основные принципы ● актор – это некая сущность, обладающая поведением; ● акторы реагируют на входящие сообщения; ● получив сообщение актор может: ○ отослать некоторое (конечное) количество сообщений другим акторам; ○ создать некоторое (конечное) количество новых акторов; ○ определить для себя новое поведение для обработки последующих сообщений. 12
  13. 13. Актор – это некоторая сущность Не более того. Актором может быть отдельный процесс. Например, Erlang. Актором может быть отдельный поток (OS thread, "green" thread, fiber). Например, goroutine в Go может рассматриваться как актор. Актором может быть объект, которому кто-то выделяет рабочий контекст. Например, Akka. 13
  14. 14. Модель Акторов (официоз №1) Появилась в 1973-ем году благодаря работам Карла Хьюитта (Carl Hewitt). Была затем развита в 1981-ом Уильямом Клингером (William Clinger). И в 1985-ом Гулом Агха (Gul Agha). Это если говорить про формализацию Модели Акторов. Неформально она открывалась и переоткрывалась множество раз. 14
  15. 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. Вопрос ребром! Почему Модель Акторов привлекает внимание? 16
  17. 17. Чаще всего упоминается: 1. Простота. Нет разделяемых данных. Асинхронный обмен сообщениями. 2. Аналогии с реальным миром. Многие вещи знакомы и понятны. 3. Масштабирование. Акторов может быть хоть миллион. Акторы могут жить на разных нодах. 4. Отказоустойчивость. Какие-то акторы могут "падать", другие акторы это обнаружат и исправят (см. систему супервизоров Erlang-а). Shared nothing + message-passing сильно помогают и тут. 17
  18. 18. Модель Акторов как... ...способ смотреть на мир. Другими словами: если у вас в руках молоток... 18
  19. 19. Вопрос на миллион Модель Акторов ‒ это серебряная пуля? 19
  20. 20. Границы применимости Есть несколько маркеров, которые могут подсказать, что выбор Модели Акторов будет оправданным. Могут подсказать. А могут и не подсказать. 20
  21. 21. Маркер №1 Fire-and-Forget 21
  22. 22. Fire-and-Forget 1. Тотальный контроль не нужен. 2. Результаты начатых операций вот прямо здесь и сейчас не нужны. 3. Если что-то не случилось, то ничего страшного. Мы постоянно используем этот принцип в повседневной жизни 22
  23. 23. Fire-and-Forget: примеры "за" и "против" Пример №1: две рабочие нити, одна только читает данные, вторая только разбирает их. Пример №2: коммит в БД. 23
  24. 24. Маркер №2 Конечные автоматы 24
  25. 25. Принципы Модели Акторов еще раз ● актор – это некая сущность, обладающая поведением; ● акторы реагируют на входящие сообщения; ● получив сообщение актор может: ○ отослать некоторое (конечное) количество сообщений другим акторам; ○ создать некоторое (конечное) количество новых акторов; ○ определить для себя новое поведение для обработки последующих сообщений. 25
  26. 26. О каких конечных автоматах речь? 26
  27. 27. Маркер №3 Архитектура Shared Nothing 27
  28. 28. Shared Nothing: контрольный в голову Могут ли сущности в программе работать без каких либо разделяемых данных? Может ли каждая сущность быть представлена в виде отдельного процесса? Это таки экстремизм. Но он отрезвляет. 28
  29. 29. Не маркер, но тем не менее Таймеры 29
  30. 30. Напрямую не связано с Моделью Акторов, но... ...таймеры в виде отложенных сообщений ‒ это очень удобно на практике. Следствие того, что акторы общаются с внешним миром только посредством сообщений. Таймеры активно используются акторами из-за принципа Fire-and-Forget. 30
  31. 31. Общие слова закончились... ...надеюсь, что пока было понятно 31
  32. 32. Ближе к телу! Модель Акторов и C++: миф или реальность? 32
  33. 33. Реальность! ● промышленная автоматизация (АСУ ТП); ● телеком; ● электронная и мобильная коммерция; ● имитационное моделирование; ● САПР; ● игростроение; ● ПО промежуточного слоя (СУБД, ...) 33
  34. 34. Есть несколько нюансов Вызванных спецификой C++: ● небезопасностью; ● специфическими прикладными нишами (embedded, high-performance, real-time и т.д.). В C++ все акторные фреймворки очень разные и непохожие. 34
  35. 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. 36. А теперь о главном SObjectizer 36
  37. 37. Давным-давно, в далекой-далекой... 1994-й год. Гомель. КБ Системного Программирования. Отдел АСУТП. Попытка сделать объектно-ориентированную SCADA-систему. 37
  38. 38. Объектная SCADA. Итог SCADA Objectizer. 1998-й год. Опробован в реальном проекте. Прекратил свое существование в начале 2000-го :( 38
  39. 39. Предпосылки к перерождению Начало 2002-го. Компания "Интервэйл". Двое участников разработки SCADA Objectizer. Небольшая GUI-программа для управления подключенными к ПК устройствами... 39
  40. 40. Апрель 2002-го года. SObjectizer = SCADA + Objectizer. SObjectizer-4 (в четвертый раз все сначала). Четвертое пришествие 40
  41. 41. Май 2002-го: начало использования SO-4 в разработке. Март 2006-го: перевод SO-4 в категорию OpenSource проектов под BSD-лицензией. Сентябрь 2010-го: начало работ над SObjectizer-5. Май 2013-го: публичный релиз SO-5 под BSD-лицензией. Июль 2013-го: SObjectizer начал жить независимо от "Интервэйл". Этапы большого пути 41
  42. 42. В компании "Интервэйл": ● SMS/USSD-агрегатор; ● куски платежных сервисов, платформ электронной коммерции; ● система мониторинга для DevOps. Вне "Интервэйла": ● имитационное моделирование; ● тестовые стенды; ● управление оборудованием; ● прототипирование. Применение 42
  43. 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. ...мы не делали реализацию Модели Акторов. На самом деле... 44
  45. 45. ...упростить для себя разработку многопоточного кода. На момент начала работ над SObjectizer мы даже не знали про Модель Акторов вообще. Но получилась одна из возможных реализаций Модели Акторов. Мы лишь хотели... 45
  46. 46. Хватит общих слов! Что мы получили в SObjectizer-5? 46
  47. 47. Агенты. Кооперации агентов. (Агент == Актор и наоборот) Сообщения. Подписки на сообщения. Почтовые ящики. Диспетчеры. Из чего же, из чего же, из чего же... 47
  48. 48. Все взаимодействие между агентами в SObjectizer идет только через асинхронные сообщения. Сообщения это объекты. Тип сообщения ‒ это ключ для поиска обработчиков сообщения. В SObjectizer есть сообщения 48
  49. 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. 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. 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. 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. 53. struct get_status : public so_5::signal_t {}; Еще есть сигналы 53
  54. 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. 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. 56. В "традиционной" Модели Акторов адресатом сообщения является актор. В SObjectizer сообщения отсылаются в "почтовый ящик". Почтовый ящик в SObjectizer называется mbox. Что такое Target для send? 56
  57. 57. Подписка на сообщение из mbox-а 57
  58. 58. MPMC (Multi-Producer/Multi-Consumer) любой может послать, любой может подписаться. MPSC (Multi-Producer/Single-Consumer) любой может послать, только владелец может подписаться, автоматически создается для каждого агента, так же называется direct mbox. Два типа mbox-ов 58
  59. 59. MPMC (Multi-Producer/Multi-Consumer) любой может послать, любой может подписаться. MPSC (Multi-Producer/Single-Consumer) любой может послать, только владелец может подписаться, автоматически создается для каждого агента, так же называется direct mbox. Два типа mbox-ов 59 Использование MPSC mbox-ов и дает максимальное приближение к Модели Акторов.
  60. 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. 61. // Подписка на сообщение из mbox-а. so_subscribe(some_mbox).event(... /* обработчик */); // Подписка на сообщение из собственного MPSC-mbox-а. so_subscribe_self().event(... /* обработчик */); Подписка на сообщения 61
  62. 62. // Подписка на сообщение из mbox-а. so_subscribe(some_mbox).event(... /* обработчик */); // Подписка на сообщение из собственного MPSC-mbox-а. so_subscribe_self().event(... /* обработчик */); Подписка на сообщения 62 Всего лишь более компактная запись для: so_subscribe(so_direct_mbox()).event(... /* обработчик */);
  63. 63. Именно диспетчер определяет где и когда агент будет обрабатывать свои сообщения. Может быть запущено сразу несколько диспетчеров. Поэтому каждый агент должен быть привязан к какому-то диспетчеру. В SObjectizer есть диспетчеры 63
  64. 64. В SObjectizer есть диспетчеры 64
  65. 65. 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. 66. Зачем так много? 66
  67. 67. Зачем так много? 67 Иногда может потребоваться сделать диспетчер под задачу: https://habr.com/post/353712/
  68. 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. 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. 70. Состояния позволяют реализовывать сложные иерархические конечные автоматы: ● вложенные состояния; ● on_enter/on_exit для состояний; ● time_limit для состояний; ● deep/shallow history для состояний. У агентов есть состояния 70
  71. 71. Состояния позволяют реализовывать сложные иерархические конечные автоматы: ● вложенные состояния; ● on_enter/on_exit для состояний; ● time_limit для состояний; ● deep/shallow history для состояний. У агентов есть состояния 71 Опять же все потому, что корни растут из АСУТП.
  72. 72. Пример агента с состояниями 72
  73. 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. 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. 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. 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. 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. 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. 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. 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. 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. 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. 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. 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. 85. off.event([&](mhood_t<turn_on_off>) { ... /* специфический для инициализации устройства код */ this >>= on; }); on.event([&](mhood_t<turn_on_off>) { ... /* специфический для деинициализации устройства код */ this >>= off; }); Пример агента с состояниями 85
  86. 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. 87. off.event([&](mhood_t<turn_on_off>) { ... /* специфический для инициализации устройства код */ this >>= on; }); on.event([&](mhood_t<turn_on_off>) { ... /* специфический для деинициализации устройства код */ this >>= off; }); Пример агента с состояниями 87
  88. 88. Кооперации агентов решают задачу одномоментной регистрации в SObjectizer группы взаимосвязанных агентов. Кооперации агентов 88
  89. 89. Кооперации агентов 89
  90. 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. Наполнение и регистрация коопераций 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. 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. Наполнение и регистрация коопераций 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. Наполнение и регистрация коопераций 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. 95. Два агента: pinger и ponger. Агент pinger отсылает сигналы ping агенту ponger. Агент ponger в ответ отсылает сигналы pong. Работают некоторое время. В конце агенты сообщают сколько сообщений получили. Классика жанра: ping-pong 95
  96. 96. Первая на базе MPMC (multi-consumer) mbox-ов. Вторая на базе MPSC (single-consumer или direct) mbox-ов. Посмотрим две реализации 96
  97. 97. Первая на базе MPMC (multi-consumer) mbox-ов. Вторая на базе MPSC (single-consumer или direct) mbox-ов. Посмотрим две реализации 97 Достаточно одного MPMC-mbox-а. Агент pinger подписывается на pong. Агент ponger подписывается на ping. Разные подписки. Поэтому MPMC точно знает кому и что доставлять. Практически как в случае одного MQ-брокера и двух топиков с разными именами.
  98. 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. 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. 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. 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. 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. 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. 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. 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. 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. 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. 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. 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. 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. 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. 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. 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. 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. 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. 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. 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. 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. 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. 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. 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. 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. 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. 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. 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. 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. 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. 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. 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. 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. 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. 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. 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. 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. 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. 136. А что, если поместить pinger-а и ponger-а на одну общую нить? *_one_thread 136
  137. 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. 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. 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. 140. Когда pinger и ponger работают на разных рабочих нитях: *_one_thread. Пояснение 140 pinger ponger
  141. 141. Когда pinger и ponger работают на разных рабочих нитях: *_one_thread. Пояснение 141 pinger ponger Моменты, когда входящая очередь пуста и нить вынуждена ждать на пустой очереди.
  142. 142. Хватит синтетики! Более-менее реальный пример 142
  143. 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. 144. SObjectizer используется для распараллеливания обработки запросов. Агенты двух типов: a_transform_manager_t a_transformer_t Демо-проект Shrimp (2) 144
  145. 145. Демо-проект Shrimp (3) 145
  146. 146. void a_transformer_t::so_define_agent() { so_subscribe_self().event( &a_transformer_t::on_resize_request ); } Демо-проект Shrimp (4) 146
  147. 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. 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. 149. Итоги? Что можно сказать после 16-ти лет в обнимку с SObjectizer? 149
  150. 150. Которые уже произносились неоднократно. На C++Russia 2017: Традиционные банальности 150
  151. 151. Не для всех задач Модель Акторов применима. Но там, где применима удобный фреймворк рулит и бибикает. Тем более, что SObjectizer ‒ это не только Actor Model, но и Pub-Sub, и CSP. К хорошему быстро привыкаешь 151
  152. 152. Модель акторов имеет смысл использовать и в C++. Нет надобности велосипедить. Есть из чего выбирать. Название правильного фреймворка начинается на SObjec... ;) Итого: 152
  153. 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

×