SlideShare a Scribd company logo
C++ Russia 2018
Акторы для C++:
стоило ли оно того?
Евгений Охотников
Коротко о себе
Начал программировать в 1990-ом.
В обнимку c C++ c 1992-го.
Был разработчиком, руководителем отдела, руководителем
управления, совладельцем софтверной компании.
Насмотрелся на C++, его плюсы и минусы с разных сторон.
С 2002-го года отвечаю за разработку SObjectizer.
2
SObjectizer
Инструмент для упрощения разработки многопоточных и/или
событийно-ориентированных приложений на C++.
Комбинирует идеи из Actor Model, Publish-Subscribe и CSP.
Живет на SourceForge + зеркало на github.
Рассказ о SObjectizer был на C++Russia 2017:
● слайды;
● видео.
3
Давным-давно, в далекой-далекой...
SObjectizer имеет длительную историю, но мы начнем лишь с 2002-
го года.
2002: начало разработки и эксплуатации SObjectizer-4.
С++98. Но с оглядкой на VC++ 6.0 и старые версии GCC.
2010: начало разработки SObjectizer-5.
Сразу на C++0x. Без нормальной его поддержки в компиляторах.
4
Бочка дегтя...
Что в С++ мешает разработке
акторного фреймворка?
5
Да кому C++ вообще нужен?
Огромная конкуренция со стороны других языков
программирования.
Java, C#, Scala, Python, Erlang, Go, Kotlin, Rust...
Объективно, C++ ‒ это нишевый язык. Для большого количества
задач есть лучшие альтернативы.
Классический вопрос:
А зачем нужен ваш SObjectizer, если есть Erlang, Akka и Go?
6
C++ везде очень разный
В разработке десктоп-приложений C++ свой.
В разработке hard real-time С++ свой.
В разработке high performance computing С++ свой.
В разработке embedded systems С++ свой.
Яркий пример: отношение к использованию исключений.
7
Управление зависимостями ‒ боль
В нормальных языках есть Maven, Cargo, Cabal, RubyGems, ...
В С++: buckaroo, build2, cget, conan, conda, cpm, cppan, hunter, vcpkg, ...
Только де-факто стандарта нет :(
Де-факто есть CMake* + целый зоопарк: bazel, build2, buckaroo,
meson, scons, waf. Ну и старый добрый make.
Написать что-то свое иногда проще, чем подключить чужое :(
8
* Отличный пример Worse Is Better :(
Есть C++ в стандарте, а есть C++...
...в вашем компиляторе.
Иногда это сильно разные C++.
Особенно в прошлом.
Не все имеют возможность сидеть на самых новых компиляторах.
Боль для разработчиков библиотек. Как пример: использование
подмножества C++11 через несколько лет после выхода C++14.
9
Компилятор влияет на дизайн (1-1)
Variadic templates из C++11
API когда поддержки variadic templates нет:
struct my_message : public so_5::message_t {
...
my_message(int a, int b, int c) : ... {}
};
const so_5::mbox_t & target = ...;
unique_ptr<my_message> msg(new my_message(1, 2, 3));
target->deliver_message(std::move(msg));
10
Компилятор влияет на дизайн (1-2)
Variadic templates из C++11
API когда поддержка variadic templates есть:
struct my_message : public so_5::message_t {
...
my_message(int a, int b, int c) : ... {}
};
const so_5::mbox_t & target = ...;
so_5::sent<my_message>(target, 1, 2, 3);
11
Компилятор влияет на дизайн (2)
noexcept из C++11
constexpr из C++11/14
Перегрузка методов для & и && (актуально для builder pattern).
До сих пор забываю использовать вышеперечисленное.
Приходится обкладываться макросами для VC++12.0.
12
Нет compile-time рефлексии (1)
hello_world из SObjectizer-4. Чисто C++ная часть.
13
class a_hello_t : public so_4::rt::agent_t
{
public :
a_hello_t() : so_4::rt::agent_t("a_hello") {}
virtual const char * so_query_type() const;
virtual void so_on_subscription() {
so_subscribe( "evt_start",
so_4::rt::sobjectizer_agent_name(), "msg_start" );
so_subscribe( "evt_hello", "msg_hello" );
}
struct msg_hello {};
void evt_start() {
so_4::api::send_msg(so_query_name(), "msg_hello", 0);
}
void evt_hello() {
std::cout << "Hello, World!" << std::endl;
so_4::api::send_msg(
so_4::rt::sobjectizer_agent_name(),
"msg_normal_shutdown", 0 );
}
};
Нет compile-time рефлексии (2)
Метаописание для SObjectizer-а.
14
// Описание класса агента для SObjectizer-а.
SOL4_CLASS_START( a_hello_t )
// Описание сообщений.
SOL4_MSG_START( msg_hello, a_hello_t::msg_hello )
SOL4_MSG_FINISH()
// Описание событий.
SOL4_EVENT( evt_start )
SOL4_EVENT( evt_hello )
// Описание единственного состояния.
SOL4_STATE_START( st_initial )
SOL4_STATE_EVENT( evt_start )
SOL4_STATE_EVENT( evt_hello )
SOL4_STATE_FINISH()
SOL4_CLASS_FINISH()
Скудная стандартная библиотека
Раньше даже hash-tables нужно было искать на стороне.
В C++11/14/17 уже сильно получше. Но до JDK далеко :(
Многое до сих пор нужно искать на стороне.
Очень уж разные вкусы у C++ников: Boost, ACE, libev, libuv и т.д.
Чтобы ты не выбрал, будет много тех, кому твой выбор не нравится.
15
С++ ‒ это язык без Garbage Collector-а
Странная претензия для C++, но...
...при разработке многопоточного кода GC очень сильно облегчает
жизнь.
Ну очень сильно.
16
Продолжаем набрасывать...
Особенности с непонятным
знаком...
17
Fail-fast в C++ ‒ это совсем не то...
...что в Erlang-е или даже в Go.
Деление на ноль.
Не обработанное на верхнем уровне исключение.
Представьте, что это делает нить в многопоточном C++
приложении.
18
Многословность C++
Scala:
class Point(var x: Int, var y: Int) {
...
}
C++:
class point {
int x_, y_;
public:
point(int x, int y) : x_{x}, y_{y} {}
...
};
19
Пример: конкурентный HelloWorld на Go
20
Пример: конкурентный HelloWorld на Go
func main() {
sayHello := make(chan string)
sayWorld := make(chan string)
quitter := make(chan string)
go func() {
for i := 0; i < 1000; i++ {
fmt.Print("Hello ")
sayWorld <- "Do it"
<-sayHello
}
sayWorld <- "Quit"
}()
go func() {
for {
var reply string
reply = <-sayWorld
if (reply == "Quit") {
break
}
fmt.Print("World!n")
sayHello <- "Do it"
}
quitter <- "Done"
}()
<-quitter
}
21
Пример: конкурентный HelloWorld на SO-5
22
Пример: конкурентный HelloWorld на SO-5
23
int main() {
wrapped_env_t sobj;
auto say_hello = create_mchain( sobj );
auto say_world = create_mchain( sobj );
thread hello{ [&] {
string reply;
for( int i = 0; i != 1000; ++i ) {
cout << "Hello ";
say_world << "Do it"s;
say_hello >> reply;
}
say_world << "Quit"s;
} };
thread world{ [&] {
for(;;) {
string reply;
say_world >> reply;
if( "Quit" == reply )
break;
cout << "World" << endl;
say_hello << "Do it"s;
}
} };
auto_join(hello, world);
}
24
func main() {
sayHello := make(chan string)
sayWorld := make(chan string)
quitter := make(chan string)
go func() {
for i := 0; i < 1000; i++ {
fmt.Print("Hello ")
sayWorld <- "Do it"
<-sayHello
}
sayWorld <- "Quit"
}()
go func() {
for {
var reply string
reply = <-sayWorld
if (reply == "Quit") {
break
}
fmt.Print("World!n")
sayHello <- "Do it"
}
quitter <- "Done"
}()
<-quitter
}
int main() {
wrapped_env_t sobj;
auto say_hello = create_mchain( sobj );
auto say_world = create_mchain( sobj );
thread hello{ [&] {
string reply;
for( int i = 0; i != 1000; ++i ) {
cout << "Hello ";
say_world << "Do it"s;
say_hello >> reply;
}
say_world << "Quit"s;
} };
thread world{ [&] {
for(;;) {
string reply;
say_world >> reply;
if( "Quit" == reply )
break;
cout << "World" << endl;
say_hello << "Do it"s;
}
} };
auto_join(hello, world);
}
25
func main() {
sayHello := make(chan string)
sayWorld := make(chan string)
quitter := make(chan string)
go func() {
for i := 0; i < 1000; i++ {
fmt.Print("Hello ")
sayWorld <- "Do it"
<-sayHello
}
sayWorld <- "Quit"
}()
go func() {
for {
var reply string
reply = <-sayWorld
if (reply == "Quit") {
break
}
fmt.Print("World!n")
sayHello <- "Do it"
}
quitter <- "Done"
}()
<-quitter
}
int main() {
wrapped_env_t sobj;
auto say_hello = create_mchain( sobj );
auto say_world = create_mchain( sobj );
thread hello{ [&] {
string reply;
for( int i = 0; i != 1000; ++i ) {
cout << "Hello ";
say_world << "Do it"s;
say_hello >> reply;
}
say_world << "Quit"s;
} };
thread world{ [&] {
for(;;) {
string reply;
say_world >> reply;
if( "Quit" == reply )
break;
cout << "World" << endl;
say_hello << "Do it"s;
}
} };
auto_join(hello, world);
}
#include <iostream>
#include <thread>
#include <so_5/all.hpp>
using namespace std;
using namespace std::literals;
using namespace so_5;
template< typename T >
void operator<<( mchain_t & to, T && o ) {
send< T >( to, forward<T>(o) );
}
template< typename T >
void operator>>( mchain_t & from, T & o ) {
receive( from, infinite_wait, [&o]( const T & msg ) { o = msg; } );
}
Макросы и препроцессор
Мало кто любит. Ждут C++ без макросов. Когда-то в светлом
будущем...
Но сейчас более чем востребованы:
● несовместимости между компиляторами;
● системно-зависимые вещи;
● отладочно-трассировочный код;
● средство борьбы с boilerplate code;
● особенности поведения компиляторов с -Wall, -Weverything...
26
Макросы, препроцессор и -Weverything
// Воспользуемся __has_cpp_attribute, если доступен.
#if defined(__has_cpp_attribute)
// clang-4 и clang-5 ругаются при попытках использовать
// [[nodiscard]] совместно с -std=c++11 и -std=c++14
#if __has_cpp_attribute(nodiscard) && 
!(defined(SO_5_CLANG) && __cplusplus < 201703L)
#define SO_5_NODISCARD [[nodiscard]]
#endif
#endif
// Уточняем что получилось с nodiscard.
#if !defined( SO_5_NODISCARD )
#define SO_5_NODISCARD
#endif
27
Ложка мёда ;)
Чем C++ хорош?
28
Поговорим далеко не обо всем...
В обзор не вошли очень важные особенности С++:
● унифицированная система типов;
● конструкторы-деструкторы, RAII;
● пространства имен;
● const-методы (в том числе const-методы + mutable);
● лямбды;
● ...
29
Скорость исполнения
C++ в числе безусловных лидеров по скорости работы кода.
Огромное количество ресурсов уже вложено в оптимизирующие
компиляторы.
Добавление дополнительных уровней абстракции в C++ может
обходиться дешевле, чем в других языках программирования.
30
Поддержка ООП
31
Это сейчас ООП модно ругать и еще более модно от него
отказываться.
Но ООП отлично работало и 30-ть, и 20-ть лет назад.
И сейчас оно хорошо работает. Просто реже нужно.
В C++ очень хорошая поддержка ООП.
Лучше только в Eiffel, но в Eiffel есть GC и куча других проблем.
typedef/using (1)
32
Просто маленький бриллиант в короне C++...
typedef/using (2)
33
Отличный способ сократить количество писанины.
template<typename Work_Thread> using dispatcher_template_t =
so_5::disp::thread_pool::common_implementation::dispatcher_t<
Work_Thread,
dispatcher_queue_t,
agent_queue_t,
params_t,
adaptation_t>;
using timer_manager_factory_t = std::function<
timer_manager_unique_ptr_t(
error_logger_shptr_t,
outliving_reference_t<timer_manager_t::elapsed_timers_collector_t>)>;
Очень сильно не хватает в Java и том же Eiffel.
typedef/using (3)
34
Один из лучших друзей кода с длинной историей.
namespace rt
{
/*!
* deprecated Obsolete in v.5.5.1. Use so_5::intrusive_ptr_t instead.
*/
template< class T > using smart_atomic_reference_t = intrusive_ptr_t< T >;
/*!
* since v.5.5.9
*
* brief A typedef for compatibility with previous versions.
* deprecated Will be removed in v.5.6.0.
*/
using agent_coop_t = so_5::coop_t;
}
typedef/using (4)
35
Удобный способ "спрятать" детали реализации.
using private_dispatcher_handle_t = so_5::intrusive_ptr_t< private_dispatcher_t >;
using demands_counter_t = std::atomic< std::size_t >;
Перегрузка (1)
36
Упрощаем жизнь пользователям по мере накопления опыта.
Было:
const so_5::agent_t & target = ...;
so_5::send<Msg>(target.so_direct_mbox(), ...);
so_5::send_delayed<Msg>(target.so_environment(), target.so_direct_mbox(), 100ms, ...);
Перегрузка (2)
37
Стало:
const so_5::agent_t & target = ...;
so_5::send<Msg>(target, ...);
so_5::send_delayed<Msg>(target, 100ms, ...);
Старый код продолжает работать.
Аргументы по умолчанию (1)
38
Расширяем функциональность при сохранении совместимости.
Было:
template<typename Lambda>
subscription_bind_t& event(Lambda && handler) {...}
...
so_subscribe(mbox)
.event([this](mhood_t<Msg> cmd){...});
Аргументы по умолчанию (2)
39
Стало:
template<typename Lambda>
subscription_bind_t& event(Lambda && handler,
thread_safety_t thread_safety = thread_safety_t::unsafe) {...}
...
so_subscribe(mbox)
.event([this](mhood_t<Msg> cmd){...})
.event([this](mhood_t<Another_Msg> cmd){...}, so_5::thread_safe);
Исключения (1)
40
Спорам "использовать или не использовать" несть числа.
Отдельное спасибо Google за его C++ Style Guide:
● все знают про Google C++ Style Guide и запрет исключений в нем;
● мало кто сумел дочитать этот гайдлайн до:
"On their face, the benefits of using exceptions outweigh the costs, especially in new projects.
However, for existing code, the introduction of exceptions has implications on all dependent
code. If exceptions can be propagated beyond a new project, it also becomes problematic to
integrate the new project into existing exception-free code. Because most existing C++ code at
Google is not prepared to deal with exceptions, it is comparatively difficult to adopt new code
that generates exceptions."
Исключения (2)
41
Кто действительно любит писать код в таком стиле?
auto msg = allocate_message<my_message>();
if(!msg)
return msg.error();
auto init_result = msg->init(1, 2, 3);
if(init_result)
return init_result;
auto send_result = target->deliver_message(std::move(*msg));
if(send_result)
return send_result;
Исключения (3)
42
Мы предпочитаем писать так:
target->deliver_message(std::make_unique<my_message>(1, 2, 3));
И исключения позволяют нам так делать не теряя в надежности.
А если называть вещи своими именами, то существенно
выигрывая в надежности.
Шаблоны. Обычные и Variadic
43
Очень и очень упрощают жизнь в C++.
Активно используются в SObjectizer (для версии 5.5.22):
http://cloc.sourceforge.net v 1.64 T=1.76 s (111.4 files/s, 35211.6 lines/s)
-------------------------------------------------------------------------------
Language files blank comment code
-------------------------------------------------------------------------------
C/C++ Header 133 6167 21581 20290
C++ 61 2634 2869 10675
CMake 2 29 0 183
Ruby 4 56 2 135
Шаблоны. Обычные
44
Фрагмент реализации диспетчеров (С++11):
template<typename Demand_Queue, template<class> class Work_Thread >
class work_thread_template_t : public Work_Thread< Demand_Queue >
{
auto pop_demand() -> decltype(std::declval<Demand_Queue>().pop()) {...}
...
};
...
template< typename Demand_Queue >
using work_thread_no_activity_tracking_t =
work_thread_template_t<Demand_Queue, work_thread_details::no_activity_tracking_impl_t >;
template< typename Demand_Queue >
using work_thread_with_activity_tracking_t =
work_thread_template_t<Demand_Queue, work_thread_details::with_activity_tracking_impl_t >;
Шаблоны. Variadic
45
Не самая простая, но удобная шаблонная функция:
template< typename Timeout, typename... Handlers >
inline mchain_receive_result_t receive(
const so_5::mchain_t & chain,
Timeout waiting_timeout,
Handlers &&... handlers ) {...}
Позволяет писать почти как в Go:
receive(channel, so_5::infinite_wait,
[&](mhood_t<new_request> cmd) {...},
[&](mhood_t<check_status> cmd) {...},
[&](mhood_t<cancel_request> cmd) {...},
[&](mhood_t<reconfigure> cmd) {...});
Пора переходить к итогам
Был ли смысл в создании
акторного фреймворка для C++?
46
Да!
47
Модель Акторов ‒ это один из способов упростить разработку.
Как и Publish-Subscribe. Как и CSP.
Использование голых нитей и mutex-ов в C++ ‒ это пот, боль и
кровь.
Чем более высокоуровневый инструмент в руках у разработчика,
тем проще, быстрее и надежнее.
Не забываем про скорость работы C++ кода.
И еще об итогах...
Легко ли "продать" акторный
фреймворк для C++?
48
Нет!
49
Ниша C++ сама по себе сжимается со временем.
Большое количество удобных альтернатив (Erlang, Akka, Go).
Особенно для задач, где Actor Model, Pub/Sub и CSP востребованны.
С++ в разных предметных областях очень разный. Невозможно
создать инструмент, который был бы одинаково удобен в embbeded,
HPC и desktop.
NIH-синдром и C++-сообщество. Зачастую это объективно, см.
предыдущий пункт.
Спасибо за терпение!
Вопросы?
50
SObjectizer: https://sourceforge.net/p/sobjectizer или
https://github.com/eao197/so-5-5
Документация по SObjectizer: https://sourceforge.net/p/sobjectizer/wiki/Home/
Статьи о SObjectizer на русском: https://habrahabr.ru/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
51

More Related Content

PPTX
Александр Тарасенко, Использование python для автоматизации отладки С/C++ код...
PPTX
Евгений Зуев, С++ в России: Стандарт языка и его реализация
PPTX
Ecma script 6 yevhen diachenko
PDF
хитрости выведения типов
PDF
Цена ошибки
PDF
Павел Довгалюк, Обратная отладка
PPTX
Александр Фокин, Рефлексия в C++
PPTX
Сергей Шамбир, Адаптация Promise/A+ для взаимодействия между C++ и Javascript
Александр Тарасенко, Использование python для автоматизации отладки С/C++ код...
Евгений Зуев, С++ в России: Стандарт языка и его реализация
Ecma script 6 yevhen diachenko
хитрости выведения типов
Цена ошибки
Павел Довгалюк, Обратная отладка
Александр Фокин, Рефлексия в C++
Сергей Шамбир, Адаптация Promise/A+ для взаимодействия между C++ и Javascript

What's hot (20)

PDF
О сложностях программирования, или C# нас не спасет?
PDF
Исключения C++ через призму компиляторных оптимизаций. Роман Русяев ➠ CoreHa...
PDF
Борис Сазонов, RAII потоки и CancellationToken в C++
PDF
Юрий Ефимочев, Компилируемые в реальном времени DSL для С++
PPTX
Евгений Рыжков, Андрей Карпов Как потратить 10 лет на разработку анализатора ...
ODP
Как за час сделать недельную работу
PDF
Андрей Карпов, Приватные байки от разработчиков анализатора кода
PDF
DI в C++ тонкости и нюансы
PPT
Юнит-тестирование и Google Mock. Влад Лосев, Google
PDF
PVS-Studio vs Chromium
PDF
Дмитрий Кашицын, Вывод типов в динамических и не очень языках I
PPTX
Некоторые паттерны реализации полиморфного поведения в C++ – Дмитрий Леванов,...
PPTX
Использование шаблонов и RTTI для конфигурации симулятора флеш-накопителя - Г...
PDF
Особенности создания XS-модулей на языке C++. Владимир Тимофеев. Moscow.pm 4 ...
PDF
Полухин Антон, Как делать не надо: C++ велосипедостроение для профессионалов
PDF
Для чего мы делали свой акторный фреймворк и что из этого вышло?
PPTX
C++ Core Guidelines
PDF
Сравнение статического анализа общего назначения из Visual Studio 2010 и PVS-...
PDF
Дмитрий Кашицын, Троллейбус из буханки: алиасинг и векторизация в LLVM
PDF
Дмитрий Прокопцев — R-ссылки в С++11
О сложностях программирования, или C# нас не спасет?
Исключения C++ через призму компиляторных оптимизаций. Роман Русяев ➠ CoreHa...
Борис Сазонов, RAII потоки и CancellationToken в C++
Юрий Ефимочев, Компилируемые в реальном времени DSL для С++
Евгений Рыжков, Андрей Карпов Как потратить 10 лет на разработку анализатора ...
Как за час сделать недельную работу
Андрей Карпов, Приватные байки от разработчиков анализатора кода
DI в C++ тонкости и нюансы
Юнит-тестирование и Google Mock. Влад Лосев, Google
PVS-Studio vs Chromium
Дмитрий Кашицын, Вывод типов в динамических и не очень языках I
Некоторые паттерны реализации полиморфного поведения в C++ – Дмитрий Леванов,...
Использование шаблонов и RTTI для конфигурации симулятора флеш-накопителя - Г...
Особенности создания XS-модулей на языке C++. Владимир Тимофеев. Moscow.pm 4 ...
Полухин Антон, Как делать не надо: C++ велосипедостроение для профессионалов
Для чего мы делали свой акторный фреймворк и что из этого вышло?
C++ Core Guidelines
Сравнение статического анализа общего назначения из Visual Studio 2010 и PVS-...
Дмитрий Кашицын, Троллейбус из буханки: алиасинг и векторизация в LLVM
Дмитрий Прокопцев — R-ссылки в С++11
Ad

Similar to Акторы на C++: стоило ли оно того? (20)

PDF
Cpp0x Introduction
PPTX
C++0x
PPTX
Эффективный C++
PDF
Незаменимый С++. Антон Полухин. CoreHard Spring 2019
PDF
Объектно-Ориентированное Программирование на C++, Лекции 1 и 2
PDF
C++ Базовый. Занятие 04.
PPTX
SECON'2016. Чубарь Алексей, Мобильные грабли Unity
PDF
Лекция 8: Многопоточное программирование: Intel Threading Building Blocks
PPTX
Статический и динамический полиморфизм в C++, Дмитрий Леванов
PPTX
Статический и динамический полиморфизм в C++, Дмитрий Леванов
PDF
20130429 dynamic c_c++_program_analysis-alexey_samsonov
PDF
Разница в подходах анализа кода компилятором и выделенным инструментом
PDF
Шишки, набитые за 15 лет использования акторов в C++
PDF
шишки, набитые за 15 лет использования акторов в c++ v.001.3
PPTX
Принципы работы статического анализатора кода PVS-Studio
PDF
Лекция 8. Intel Threading Building Blocks
PDF
GitLab, Prometheus и Grafana с Kubernetes
PDF
Opensource на .NET
PPTX
C++ CoreHard Autumn 2018. Что не умеет оптимизировать компилятор - Александр ...
Cpp0x Introduction
C++0x
Эффективный C++
Незаменимый С++. Антон Полухин. CoreHard Spring 2019
Объектно-Ориентированное Программирование на C++, Лекции 1 и 2
C++ Базовый. Занятие 04.
SECON'2016. Чубарь Алексей, Мобильные грабли Unity
Лекция 8: Многопоточное программирование: Intel Threading Building Blocks
Статический и динамический полиморфизм в C++, Дмитрий Леванов
Статический и динамический полиморфизм в C++, Дмитрий Леванов
20130429 dynamic c_c++_program_analysis-alexey_samsonov
Разница в подходах анализа кода компилятором и выделенным инструментом
Шишки, набитые за 15 лет использования акторов в C++
шишки, набитые за 15 лет использования акторов в c++ v.001.3
Принципы работы статического анализатора кода PVS-Studio
Лекция 8. Intel Threading Building Blocks
GitLab, Prometheus и Grafana с Kubernetes
Opensource на .NET
C++ CoreHard Autumn 2018. Что не умеет оптимизировать компилятор - Александр ...
Ad

More from Yauheni Akhotnikau (20)

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

Акторы на C++: стоило ли оно того?

  • 1. C++ Russia 2018 Акторы для C++: стоило ли оно того? Евгений Охотников
  • 2. Коротко о себе Начал программировать в 1990-ом. В обнимку c C++ c 1992-го. Был разработчиком, руководителем отдела, руководителем управления, совладельцем софтверной компании. Насмотрелся на C++, его плюсы и минусы с разных сторон. С 2002-го года отвечаю за разработку SObjectizer. 2
  • 3. SObjectizer Инструмент для упрощения разработки многопоточных и/или событийно-ориентированных приложений на C++. Комбинирует идеи из Actor Model, Publish-Subscribe и CSP. Живет на SourceForge + зеркало на github. Рассказ о SObjectizer был на C++Russia 2017: ● слайды; ● видео. 3
  • 4. Давным-давно, в далекой-далекой... SObjectizer имеет длительную историю, но мы начнем лишь с 2002- го года. 2002: начало разработки и эксплуатации SObjectizer-4. С++98. Но с оглядкой на VC++ 6.0 и старые версии GCC. 2010: начало разработки SObjectizer-5. Сразу на C++0x. Без нормальной его поддержки в компиляторах. 4
  • 5. Бочка дегтя... Что в С++ мешает разработке акторного фреймворка? 5
  • 6. Да кому C++ вообще нужен? Огромная конкуренция со стороны других языков программирования. Java, C#, Scala, Python, Erlang, Go, Kotlin, Rust... Объективно, C++ ‒ это нишевый язык. Для большого количества задач есть лучшие альтернативы. Классический вопрос: А зачем нужен ваш SObjectizer, если есть Erlang, Akka и Go? 6
  • 7. C++ везде очень разный В разработке десктоп-приложений C++ свой. В разработке hard real-time С++ свой. В разработке high performance computing С++ свой. В разработке embedded systems С++ свой. Яркий пример: отношение к использованию исключений. 7
  • 8. Управление зависимостями ‒ боль В нормальных языках есть Maven, Cargo, Cabal, RubyGems, ... В С++: buckaroo, build2, cget, conan, conda, cpm, cppan, hunter, vcpkg, ... Только де-факто стандарта нет :( Де-факто есть CMake* + целый зоопарк: bazel, build2, buckaroo, meson, scons, waf. Ну и старый добрый make. Написать что-то свое иногда проще, чем подключить чужое :( 8 * Отличный пример Worse Is Better :(
  • 9. Есть C++ в стандарте, а есть C++... ...в вашем компиляторе. Иногда это сильно разные C++. Особенно в прошлом. Не все имеют возможность сидеть на самых новых компиляторах. Боль для разработчиков библиотек. Как пример: использование подмножества C++11 через несколько лет после выхода C++14. 9
  • 10. Компилятор влияет на дизайн (1-1) Variadic templates из C++11 API когда поддержки variadic templates нет: struct my_message : public so_5::message_t { ... my_message(int a, int b, int c) : ... {} }; const so_5::mbox_t & target = ...; unique_ptr<my_message> msg(new my_message(1, 2, 3)); target->deliver_message(std::move(msg)); 10
  • 11. Компилятор влияет на дизайн (1-2) Variadic templates из C++11 API когда поддержка variadic templates есть: struct my_message : public so_5::message_t { ... my_message(int a, int b, int c) : ... {} }; const so_5::mbox_t & target = ...; so_5::sent<my_message>(target, 1, 2, 3); 11
  • 12. Компилятор влияет на дизайн (2) noexcept из C++11 constexpr из C++11/14 Перегрузка методов для & и && (актуально для builder pattern). До сих пор забываю использовать вышеперечисленное. Приходится обкладываться макросами для VC++12.0. 12
  • 13. Нет compile-time рефлексии (1) hello_world из SObjectizer-4. Чисто C++ная часть. 13 class a_hello_t : public so_4::rt::agent_t { public : a_hello_t() : so_4::rt::agent_t("a_hello") {} virtual const char * so_query_type() const; virtual void so_on_subscription() { so_subscribe( "evt_start", so_4::rt::sobjectizer_agent_name(), "msg_start" ); so_subscribe( "evt_hello", "msg_hello" ); } struct msg_hello {}; void evt_start() { so_4::api::send_msg(so_query_name(), "msg_hello", 0); } void evt_hello() { std::cout << "Hello, World!" << std::endl; so_4::api::send_msg( so_4::rt::sobjectizer_agent_name(), "msg_normal_shutdown", 0 ); } };
  • 14. Нет compile-time рефлексии (2) Метаописание для SObjectizer-а. 14 // Описание класса агента для SObjectizer-а. SOL4_CLASS_START( a_hello_t ) // Описание сообщений. SOL4_MSG_START( msg_hello, a_hello_t::msg_hello ) SOL4_MSG_FINISH() // Описание событий. SOL4_EVENT( evt_start ) SOL4_EVENT( evt_hello ) // Описание единственного состояния. SOL4_STATE_START( st_initial ) SOL4_STATE_EVENT( evt_start ) SOL4_STATE_EVENT( evt_hello ) SOL4_STATE_FINISH() SOL4_CLASS_FINISH()
  • 15. Скудная стандартная библиотека Раньше даже hash-tables нужно было искать на стороне. В C++11/14/17 уже сильно получше. Но до JDK далеко :( Многое до сих пор нужно искать на стороне. Очень уж разные вкусы у C++ников: Boost, ACE, libev, libuv и т.д. Чтобы ты не выбрал, будет много тех, кому твой выбор не нравится. 15
  • 16. С++ ‒ это язык без Garbage Collector-а Странная претензия для C++, но... ...при разработке многопоточного кода GC очень сильно облегчает жизнь. Ну очень сильно. 16
  • 18. Fail-fast в C++ ‒ это совсем не то... ...что в Erlang-е или даже в Go. Деление на ноль. Не обработанное на верхнем уровне исключение. Представьте, что это делает нить в многопоточном C++ приложении. 18
  • 19. Многословность C++ Scala: class Point(var x: Int, var y: Int) { ... } C++: class point { int x_, y_; public: point(int x, int y) : x_{x}, y_{y} {} ... }; 19
  • 21. Пример: конкурентный HelloWorld на Go func main() { sayHello := make(chan string) sayWorld := make(chan string) quitter := make(chan string) go func() { for i := 0; i < 1000; i++ { fmt.Print("Hello ") sayWorld <- "Do it" <-sayHello } sayWorld <- "Quit" }() go func() { for { var reply string reply = <-sayWorld if (reply == "Quit") { break } fmt.Print("World!n") sayHello <- "Do it" } quitter <- "Done" }() <-quitter } 21
  • 23. Пример: конкурентный HelloWorld на SO-5 23 int main() { wrapped_env_t sobj; auto say_hello = create_mchain( sobj ); auto say_world = create_mchain( sobj ); thread hello{ [&] { string reply; for( int i = 0; i != 1000; ++i ) { cout << "Hello "; say_world << "Do it"s; say_hello >> reply; } say_world << "Quit"s; } }; thread world{ [&] { for(;;) { string reply; say_world >> reply; if( "Quit" == reply ) break; cout << "World" << endl; say_hello << "Do it"s; } } }; auto_join(hello, world); }
  • 24. 24 func main() { sayHello := make(chan string) sayWorld := make(chan string) quitter := make(chan string) go func() { for i := 0; i < 1000; i++ { fmt.Print("Hello ") sayWorld <- "Do it" <-sayHello } sayWorld <- "Quit" }() go func() { for { var reply string reply = <-sayWorld if (reply == "Quit") { break } fmt.Print("World!n") sayHello <- "Do it" } quitter <- "Done" }() <-quitter } int main() { wrapped_env_t sobj; auto say_hello = create_mchain( sobj ); auto say_world = create_mchain( sobj ); thread hello{ [&] { string reply; for( int i = 0; i != 1000; ++i ) { cout << "Hello "; say_world << "Do it"s; say_hello >> reply; } say_world << "Quit"s; } }; thread world{ [&] { for(;;) { string reply; say_world >> reply; if( "Quit" == reply ) break; cout << "World" << endl; say_hello << "Do it"s; } } }; auto_join(hello, world); }
  • 25. 25 func main() { sayHello := make(chan string) sayWorld := make(chan string) quitter := make(chan string) go func() { for i := 0; i < 1000; i++ { fmt.Print("Hello ") sayWorld <- "Do it" <-sayHello } sayWorld <- "Quit" }() go func() { for { var reply string reply = <-sayWorld if (reply == "Quit") { break } fmt.Print("World!n") sayHello <- "Do it" } quitter <- "Done" }() <-quitter } int main() { wrapped_env_t sobj; auto say_hello = create_mchain( sobj ); auto say_world = create_mchain( sobj ); thread hello{ [&] { string reply; for( int i = 0; i != 1000; ++i ) { cout << "Hello "; say_world << "Do it"s; say_hello >> reply; } say_world << "Quit"s; } }; thread world{ [&] { for(;;) { string reply; say_world >> reply; if( "Quit" == reply ) break; cout << "World" << endl; say_hello << "Do it"s; } } }; auto_join(hello, world); } #include <iostream> #include <thread> #include <so_5/all.hpp> using namespace std; using namespace std::literals; using namespace so_5; template< typename T > void operator<<( mchain_t & to, T && o ) { send< T >( to, forward<T>(o) ); } template< typename T > void operator>>( mchain_t & from, T & o ) { receive( from, infinite_wait, [&o]( const T & msg ) { o = msg; } ); }
  • 26. Макросы и препроцессор Мало кто любит. Ждут C++ без макросов. Когда-то в светлом будущем... Но сейчас более чем востребованы: ● несовместимости между компиляторами; ● системно-зависимые вещи; ● отладочно-трассировочный код; ● средство борьбы с boilerplate code; ● особенности поведения компиляторов с -Wall, -Weverything... 26
  • 27. Макросы, препроцессор и -Weverything // Воспользуемся __has_cpp_attribute, если доступен. #if defined(__has_cpp_attribute) // clang-4 и clang-5 ругаются при попытках использовать // [[nodiscard]] совместно с -std=c++11 и -std=c++14 #if __has_cpp_attribute(nodiscard) && !(defined(SO_5_CLANG) && __cplusplus < 201703L) #define SO_5_NODISCARD [[nodiscard]] #endif #endif // Уточняем что получилось с nodiscard. #if !defined( SO_5_NODISCARD ) #define SO_5_NODISCARD #endif 27
  • 28. Ложка мёда ;) Чем C++ хорош? 28
  • 29. Поговорим далеко не обо всем... В обзор не вошли очень важные особенности С++: ● унифицированная система типов; ● конструкторы-деструкторы, RAII; ● пространства имен; ● const-методы (в том числе const-методы + mutable); ● лямбды; ● ... 29
  • 30. Скорость исполнения C++ в числе безусловных лидеров по скорости работы кода. Огромное количество ресурсов уже вложено в оптимизирующие компиляторы. Добавление дополнительных уровней абстракции в C++ может обходиться дешевле, чем в других языках программирования. 30
  • 31. Поддержка ООП 31 Это сейчас ООП модно ругать и еще более модно от него отказываться. Но ООП отлично работало и 30-ть, и 20-ть лет назад. И сейчас оно хорошо работает. Просто реже нужно. В C++ очень хорошая поддержка ООП. Лучше только в Eiffel, но в Eiffel есть GC и куча других проблем.
  • 32. typedef/using (1) 32 Просто маленький бриллиант в короне C++...
  • 33. typedef/using (2) 33 Отличный способ сократить количество писанины. template<typename Work_Thread> using dispatcher_template_t = so_5::disp::thread_pool::common_implementation::dispatcher_t< Work_Thread, dispatcher_queue_t, agent_queue_t, params_t, adaptation_t>; using timer_manager_factory_t = std::function< timer_manager_unique_ptr_t( error_logger_shptr_t, outliving_reference_t<timer_manager_t::elapsed_timers_collector_t>)>; Очень сильно не хватает в Java и том же Eiffel.
  • 34. typedef/using (3) 34 Один из лучших друзей кода с длинной историей. namespace rt { /*! * deprecated Obsolete in v.5.5.1. Use so_5::intrusive_ptr_t instead. */ template< class T > using smart_atomic_reference_t = intrusive_ptr_t< T >; /*! * since v.5.5.9 * * brief A typedef for compatibility with previous versions. * deprecated Will be removed in v.5.6.0. */ using agent_coop_t = so_5::coop_t; }
  • 35. typedef/using (4) 35 Удобный способ "спрятать" детали реализации. using private_dispatcher_handle_t = so_5::intrusive_ptr_t< private_dispatcher_t >; using demands_counter_t = std::atomic< std::size_t >;
  • 36. Перегрузка (1) 36 Упрощаем жизнь пользователям по мере накопления опыта. Было: const so_5::agent_t & target = ...; so_5::send<Msg>(target.so_direct_mbox(), ...); so_5::send_delayed<Msg>(target.so_environment(), target.so_direct_mbox(), 100ms, ...);
  • 37. Перегрузка (2) 37 Стало: const so_5::agent_t & target = ...; so_5::send<Msg>(target, ...); so_5::send_delayed<Msg>(target, 100ms, ...); Старый код продолжает работать.
  • 38. Аргументы по умолчанию (1) 38 Расширяем функциональность при сохранении совместимости. Было: template<typename Lambda> subscription_bind_t& event(Lambda && handler) {...} ... so_subscribe(mbox) .event([this](mhood_t<Msg> cmd){...});
  • 39. Аргументы по умолчанию (2) 39 Стало: template<typename Lambda> subscription_bind_t& event(Lambda && handler, thread_safety_t thread_safety = thread_safety_t::unsafe) {...} ... so_subscribe(mbox) .event([this](mhood_t<Msg> cmd){...}) .event([this](mhood_t<Another_Msg> cmd){...}, so_5::thread_safe);
  • 40. Исключения (1) 40 Спорам "использовать или не использовать" несть числа. Отдельное спасибо Google за его C++ Style Guide: ● все знают про Google C++ Style Guide и запрет исключений в нем; ● мало кто сумел дочитать этот гайдлайн до: "On their face, the benefits of using exceptions outweigh the costs, especially in new projects. However, for existing code, the introduction of exceptions has implications on all dependent code. If exceptions can be propagated beyond a new project, it also becomes problematic to integrate the new project into existing exception-free code. Because most existing C++ code at Google is not prepared to deal with exceptions, it is comparatively difficult to adopt new code that generates exceptions."
  • 41. Исключения (2) 41 Кто действительно любит писать код в таком стиле? auto msg = allocate_message<my_message>(); if(!msg) return msg.error(); auto init_result = msg->init(1, 2, 3); if(init_result) return init_result; auto send_result = target->deliver_message(std::move(*msg)); if(send_result) return send_result;
  • 42. Исключения (3) 42 Мы предпочитаем писать так: target->deliver_message(std::make_unique<my_message>(1, 2, 3)); И исключения позволяют нам так делать не теряя в надежности. А если называть вещи своими именами, то существенно выигрывая в надежности.
  • 43. Шаблоны. Обычные и Variadic 43 Очень и очень упрощают жизнь в C++. Активно используются в SObjectizer (для версии 5.5.22): http://cloc.sourceforge.net v 1.64 T=1.76 s (111.4 files/s, 35211.6 lines/s) ------------------------------------------------------------------------------- Language files blank comment code ------------------------------------------------------------------------------- C/C++ Header 133 6167 21581 20290 C++ 61 2634 2869 10675 CMake 2 29 0 183 Ruby 4 56 2 135
  • 44. Шаблоны. Обычные 44 Фрагмент реализации диспетчеров (С++11): template<typename Demand_Queue, template<class> class Work_Thread > class work_thread_template_t : public Work_Thread< Demand_Queue > { auto pop_demand() -> decltype(std::declval<Demand_Queue>().pop()) {...} ... }; ... template< typename Demand_Queue > using work_thread_no_activity_tracking_t = work_thread_template_t<Demand_Queue, work_thread_details::no_activity_tracking_impl_t >; template< typename Demand_Queue > using work_thread_with_activity_tracking_t = work_thread_template_t<Demand_Queue, work_thread_details::with_activity_tracking_impl_t >;
  • 45. Шаблоны. Variadic 45 Не самая простая, но удобная шаблонная функция: template< typename Timeout, typename... Handlers > inline mchain_receive_result_t receive( const so_5::mchain_t & chain, Timeout waiting_timeout, Handlers &&... handlers ) {...} Позволяет писать почти как в Go: receive(channel, so_5::infinite_wait, [&](mhood_t<new_request> cmd) {...}, [&](mhood_t<check_status> cmd) {...}, [&](mhood_t<cancel_request> cmd) {...}, [&](mhood_t<reconfigure> cmd) {...});
  • 46. Пора переходить к итогам Был ли смысл в создании акторного фреймворка для C++? 46
  • 47. Да! 47 Модель Акторов ‒ это один из способов упростить разработку. Как и Publish-Subscribe. Как и CSP. Использование голых нитей и mutex-ов в C++ ‒ это пот, боль и кровь. Чем более высокоуровневый инструмент в руках у разработчика, тем проще, быстрее и надежнее. Не забываем про скорость работы C++ кода.
  • 48. И еще об итогах... Легко ли "продать" акторный фреймворк для C++? 48
  • 49. Нет! 49 Ниша C++ сама по себе сжимается со временем. Большое количество удобных альтернатив (Erlang, Akka, Go). Особенно для задач, где Actor Model, Pub/Sub и CSP востребованны. С++ в разных предметных областях очень разный. Невозможно создать инструмент, который был бы одинаково удобен в embbeded, HPC и desktop. NIH-синдром и C++-сообщество. Зачастую это объективно, см. предыдущий пункт.
  • 51. SObjectizer: https://sourceforge.net/p/sobjectizer или https://github.com/eao197/so-5-5 Документация по SObjectizer: https://sourceforge.net/p/sobjectizer/wiki/Home/ Статьи о SObjectizer на русском: https://habrahabr.ru/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 51