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