среда, 13 апреля 2016 г.

[prog.thoughts] Реализации Actor Model для C++: нужны ли? И если нужны, то какие?

Вынесенный в заголовок вопрос не праздный. Ну и, поскольку плотно и за свой счет занимаюсь развитием одной из таких реализаций, то очень не плохо было бы иметь хотя бы парочку ответов на этот вопрос. Желательно таких ответов, которые хорошо коррелируют с реальностью :)

Первые более-менее серьезные попытки продвижения SObjectizer в сторону англоязычной аудитории начались чуть больше года назад. А на русскоязычную аудиторию еще раньше. Очевидно, что ажиотажного спроса и широкой востребованности не случилось. Понятно, что маркетолог из меня никакой и с моей стороны допущен ряд серьезных просчетов (например, сохранение SourceForge как основной хостинговой площадки, отсутствие статей на ресурсах типа Хабра, неучастие в конференциях, неподходящий стиль общения на профильных форумах и т.д.). Однако, наверняка есть и более объективные факторы, о которых и хотелось бы сегодня поговорить.

Недавно поймал себя на ощущении, что глядя по сторонам и выбирая направления для развития SO-5, я, вероятно, смотрел не туда.

Есть впечатление, что основная движуха (речь про мейнстрим) в области упрощения concurrent programming (т.е. решения проблемы обслуживания множества независимых активностей в приложении) сейчас происходит в двух направлениях. Во-первых, Actor Model. Erlang и его реинкарнации в других языках программирования (Akka в мире JVM, Celluloid в Ruby и т.д.). Во-вторых, CSP-шные каналы, получившие очередную дозу внимания после выхода в свет языка Go (ну и реализующие подобные концепции фреймворки для других языков: Quasar для JVM, Agent для Ruby и т.д.).

Если говорить про направление Actor Model, то несложно обнаружить, что практически все существующие инструменты позиционируют себя чуть ли не одними и теми же словами. Мол, нет разделяемого состояния и это хорошо. Мол, эффективная утилизация всех имеющихся ресурсов. Мол, масштабирование, прозрачный и дешевый переход к распределенности. Мол, изоляция и отказоустойчивость, деревья супервизоров и т.д.

Ну и как следствие, практически все ориентированно на одну и ту же нишу: параллельная обработка большого количества разнородных запросов. С прицелом на Web или около того.

И вот тут возникает интересный момент. С++ в Web-е практически не применяется. А если применяется, то в двух сильно разных областях: либо ну очень сильно нагруженный Web (Google, Facebook, Yahoo, Yandex и пр.), либо же мелкие умные устройства, для управления которыми нужен Web-интерфейс. Получается, что в случае с большими нагрузками использование C++ -- это единичные случаи, со своей спецификой и мощными командами гораздо более сильных и опытных разработчиков, чем я. Эти команды скорее сами напишут нужную им реализацию Actor Model под себя, чем будут брать что-то готовое. А в случае с умными устройствами просто нет надобности в продвинутой реализации Actor Model, с поддержкой миллионов акторов, развесистыми деревьями супервизоров, прозрачной распределенность и пр. свистоперделками.

Похожая картинка, думается, будет и в мире кровавого Ынтырпрайза, в котором модель акторов может найти свое применение. Например, при интеграции разнообразных систем в общую "корпоративную шину". Хождение сообщений по таким шинам от подсистемы к системе с какой-нибудь промежуточной обработкой (трансформация, фильтрация, мониторинг и пр.) так же хорошо ложится на Actor Model. Но, опять же, эта прикладная ниша не для C++. И если там где-то C++ и есть, то это либо древний легаси, который почему-то еще жив. Либо же ну очень большие нагрузки и кастомные решения для них. Либо же это ПО промежуточного слоя (вроде какого-нибудь TIBCO Rendezvous).

В общем, складывается впечатление, что в тех нишах, где успешно применяются Erlang или Akka, C++ не будет. Да и вообще, события развиваются таким образом, что со временем C++ не будет много где. Тому способствуют не только "старые добрые" Java и C#, но и относительно молодые Go и Rust. Ну и добавим сюда, для справедливости, долгострой под названием D ;) И это пока только речь про статически типизируемые, компилируемые языки. А ведь развитие вычислительной техники, постоянный рост мощности устройств ведет к тому, что все чаще и чаще можно обойтись какими-нибудь Python-ами, Ruby или даже JavaScript-ом.

Посему возникает вопрос: а где еще C++ остается? И, если он где-то еще остается и хорошо себя чувствует, то нужны ли там реализации Actor Model? А если нужны, то какими свойствами должны обладать эти реализации?

К сожалению, точных ответов на эти вопросы у меня нет. Если бы были, то вместо написания данного поста я бы занимался реализацией какой-нибудь полезной фичи :) Но есть некоторые ощущения.

Так, на мой взгляд, C++ все еще должен себя хорошо чувствовать в области ПО промежуточного слоя. Например, реализовывать СУБД или HTTP-сервер, или MQ-сервер на C++ может быть выгоднее, чем на чистом C или на языке с GC. Особо подчеркну: может быть выгодно. Тут многое зависит от здравого смысла, опыта, религиозных убеждений и радиуса кривизны... Поскольку факторов слишком много, то может быть и не выгодно.

Плюс, в таких задачах использование многопоточности -- это не просто нормально, зачастую это неизбежно. А так как Actor Model упрощает разработку многопоточных программ (что было проверено на людях многократно), то можно надеяться, что реализации Actor Model для C++ таки нужны. Но какими свойствами должны обладать такие реализации? На этот счет есть парочка соображений.

Первое соображение касается обеспечения отказоустойчивости в стиле Erlang (т.е. линки между акторами, деревья супервизоров и пр.). Думается, что для C++ -- это в большей степени маркетинговый булшит, нежели реально востребованная фича. Если актор в C++ представляется в виде объекта, у которого время от времени дергаются какие-то методы, то этот объект не может просто "упасть", как просто падают Erlang-овские легковесные процессы если, скажем, пытаются выполнить деление на ноль. Попытка поделить на ноль в C++ном приложении, если не предпринять специальных защитных действий (сильно системнозависимых, как правило), уронит все приложение. Со всеми акторами. Включая цепочку супервизоров.

Еще один связанный с fault-tolerance аспект в C++ -- это отсутствие сборки мусора и, как следствие, возможность получения "повисших" указателей. И если акторный фреймворк еще может как-то контролировать времена жизни объектов-сообщений, то вот контроль за корректностью передаваемых внутри сообщений указателей и ссылок находится на совести программиста. Поэтому, чем сложнее программа, чем более запутано взаимодействие между акторами, чем хитрее контроль за временами жизни объектов, тем больше шансов столкнуться с "повисшим" указателем: агент A отослал агенту B ссылку на объект, затем через какое-то время грохнул этот объект, а агент B про это еще не успел узнать и дернул метод уже несуществующего объекта... Так что с изоляцией состояний акторов в C++ дела обстоят не так хорошо, как в безопасных языках со сборкой мусора.

Отпрыгну чуть в сторону. Вопросы асинхронного обмена сообщениями в сложных C++ных приложениях могут быть весьма нетривиальными. Посему мой вам совет: если вы столкнулись с такой задачей, то не спешите браться за ее решение своими силами. Лучше поищите уже готовый инструментарий, авторы которого набили кучу шишек вместо вас. Понятно, что каждый C++ник самый умный, все знает и все умеет, и на советы старпера, вроде меня, можно не обращать внимания. Однако, если вам дороги время и нервы, то не спешите делать все самостоятельно.

Второе соображение касается распределенности. Конечно, с маркетинговой точки зрения очень привлекательно выглядит возможность легко и непринужденно создавать распределенные приложения используя уже имеющиеся в акторном фреймворке средства. Вроде как ни о чем не паришься: отсылаешь сообщение, оно кем-то перехватывается, сериализуется, отсылается в канал, на другой стороне само десериализуется и доставляется получателю. Ну ведь лепота, не правда ли?

На словах да. Но ведь мы говорим про C++. А C++ сейчас редко используется там, где не нужно думать про эффективность и ресурсоемкость. Где об этом можно не думать, уже давным давно живет Erlang или Akka. И там людей, насколько я знаю, действительно, не парит как именно сообщения сериализуются/десериализуются, что за протокол используется для общения удаленных узлов, насколько этот протокол подходит для конкретной задачи.

В C++, как мне представляется, ситуация несколько иная. Одно дело, когда агенты в распределенном приложении обмениваются сотнями тысяч сообщений в секунду, но при этом: a) размер сообщений измеряется десятками байт в худшем случае и b) используется политика доставки best effort. И совсем другое дело, когда агенты изредка обмениваются громадными сообщениями, размером в сотни мегабайт (например, распределяют куски вычислительных задач по разным узлам вычислительного кластера).

Добавим сюда еще и тот факт, что C++ные куски сейчас зачастую используются в рамках мультиязычных проектов. Т.е. где-то есть части на Java, где-то части на C++, где-то части на JavaScript и т.д. Соответственно, нужно обеспечивать интероперабильность не только по форматам данных (JSON, XML, Protobuf, Thrift, ...), но и по транспорту (REST, SOAP, AMQP и т.д.).

Получается, что распределенность "из коробки" в случае C++ может быть так же полезна, как и дерево супервизоров. Т.е. прикольно, что это есть, но не более того.

Есть и другие соображения, касающиеся способности поддерживать большое количество акторов и лаконичности кода, связанного с управлением акторами, отсылкой и получением сообщений. Но эти сообращения, к сожалению, еще более сумбурны, чем описанные выше. Потому пока не буду изливать их на читателя.

Подводя некоторый итог. Думается, что нет смысла в C++ном акторном фреймворке гонятся за фичами, присутствующими в акторных фреймворков для более безопасных языков со сборкой мусора. Думается, что основная задача акторного фреймворка для C++ -- это предоставление набора механизмов для упрощения разработки сложных многопоточных приложений. В первую очередь -- механизмов для асинхронного обмена сообщениями. Думается, что для С++ного акторного фреймворка очень важна кастомизация этих механизмов под конкретные задачи. Например, управление свойствами очередей (преаллоцированные, динамически растущие или какие-то другие). Тоже самое и для сообщений (динамически создаваемые, переиспользуемые преаллоцированные, создаваемые в пулах). Т.е. если C++ используется для достижения высокого быстродействия и/или низкого потребления ресурсов, то акторный фреймворк для C++ должен помогать в этом.

Вот такие мысли касательно вынесенного в заголовок вопроса.

Интересно было бы послушать, что скажут читатели:

  • если ли по вашему мнению вообще необходимость в инструментах вроде SObjectizer, CAF или Just::Thread Pro для C++? Или же поезд давно ушел и смотреть нужно в какую-то другую сторону (Rust? Go? D?)
  • если бы вы нуждались в акторном фреймворке для C++, то что бы вы требовали от этого фреймворка в первую очередь?

Комментариев нет: