пятница, 21 августа 2015 г.

[prog.sobjectizer.thoughts] It's all about in-process message dispatching или...

...может быть уже пора отвязывать SObjectizer от Actor Model?

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

Первоначально SCADA Objectizer и SObjectizer никак не ассоциировались с Actor Model. То, что мы создавали мы сами называли агентно-ориентированными программированием. Про Actor Model, полагаю, никто из нас тогда и не знал. Что, в общем-то не удивительно, учитывая уровень доступности Интернета в наших палестинах в конце 90-х годов.

Когда начались первые попытки продвижения SObjectizer в массы оказалось, что термин агентно-ориентированное программирование уже занят. Что есть такая штука, как FIPA, и что тамошние агенты, хоть и похожи чем-то на агентов из SObjectizer, но все-таки сильно другие (на эту тему даже была написана статья-сравнение). Дабы не вводить народ в заблуждение, мы стали говорить, что SObjectizer -- это инструмент для микро-агентного программирования. Вроде как в FIPA макро-агенты, а в SObjectizer -- микро :)

Приблизительно в это же время лично я узнал и про разработанную Карлом Хьюиттом модель акторов, ту самую Actor Model, название которой сейчас у многих на слуху. Ну узнал и узнал. Какого-то сильного влияния на меня эта штука не оказала.

Во-первых, потому, что работа Хьюитта -- это чистой воды теория. Да, исследователям в области computer science эта теория нужна. Но обычным прикладным программистам требуется не теория, а конкретный инструмент. Имея же дело с конкретным инструментом на первый план выходят возможности инструмента, его ограничения, применимость к той или иной прикладной области и т.д. В общем, на мой взгляд, когда имеешь дело с использованием некого фреймворка в неком проекте (особенно коммерческом проекте, с дедлайнами и возможностью "просрать все полимеры" в случае неудачи) теоритическая база, стоящая за фреймворком, будет интересовать разработчика далеко не в первую очередь.

Во-вторых, потому, что в начале 2000-х особого интереса к Actor Model со стороны мейнстрима я не припоминаю. Вообще тогда проблемы разработки многопоточных программ и многопроцессных комплексов особо громко не обсуждались. Знаменитая работа Free Lunch is Over появилась чуть позднее. Приблизительно тогда же зародился хайп вокруг функционального программирования. А на волне этого хайпа всплыл не только Haskell с OCaml-ом, но и такой язык, как Erlang. И уже растущая популярность (ну или известность) Erlang-а дала толчок росту популярности (ну или известности) Actor Model. Но, повторюсь, в начале 2000-х, об Actor Model наслышаны были единицы, а о том, что это вообще, знало и того меньше.

В общем, особой надобности ассоциировать SObjectizer с Actor Model тогда не было. Однако, по мере роста интереса к Erlang-у, по мере распространения Akka, стало понятно, что Actor Model переходит в категорию популярных базвордов. А раз так, то почему бы этим не воспользоваться? ;) Чистой воды маркетинговый ход, результаты которого я оценивать пока не берусь.

Тем не менее, время идет, все вокруг меняется и можно еще раз подумать: а есть ли польза от того, что мы очень плотно ассоциируем SObjectizer и Actor Model?

Есть у меня сильное подозрение, что особой пользы-то и нет. Может быть даже некоторый вред наносится подобной ассоциацией. Поскольку сейчас для большинства Actor Model это то, что реализовано в Erlang и Akka (в мире C++ это C++ Actor Framework и Just Thread Pro). Соответственно, когда люди берут что-то, что вроде как является реализацией модели акторов, то они уже ждут чего-то похожего на Erlang/Akka/CAF. А когда видят что-то сильно отличное, то положительных эмоций, полагаю, у них не возникает.

По нынешнему моему ощущению, принципиальное отличие SObjectizer от вышеупомянутых фреймворков в том, что основной задачей SObjectizer является не реализация модели актеров (т.е. механизма изоляции отдельных сущностей, которые взаимодействуют друг с другом только посредством обмена сообщениями), а предоставление возможности пользователю выбирать рабочий контекст для выполнения той или иной задачи. Т.е., если говорить совсем грубо, SObjectizer был создан для того, чтобы проще было управлять рабочими нитями. Но в процессе реализации обрел черты, делающие его очень похожим на очередное воплощение Actor Model.

Дабы развернуть этот тезис чуть шире, нужно вспомнить два основных применения многопоточности.

Первое применение, возможно, наиболее известное, раскрученное и насаждаемое -- это повышение производительности. Т.е. грубо говоря, если вам нужно перегнать большой видеофайл из одного формата в другой, то в один поток это займет четыре часа, а в четыре потока -- всего час. Явная выгода от распараллеливания вычислений.

Фокус, однако, в том, что с повышением производительности за счет многопоточности все далеко не так просто, как хотелось бы. И, как это ни парадоксально, дабы получать на современной технике высокую производительность, нужно очень и очень много внимания уделять однопоточному коду и сводить межпотоковое взаимодействие к минимуму. Впрочем, это совсем на та область, для которой предназначался SObjectizer, поэтому углубляться далее нет смысла.

Второе применение многопоточности -- это параллельное выполнение нескольких независимых (или слабо зависимых) друг от друга задач. Ну, например, в почтовом клиенте вы можете читать письма, пока программа выполняет отсылку на сервер ранее написанных писем. Или же вы можете просматривать Web-страничку в одной вкладке браузера, тогда как в соседней вкладке идет подкачка и парсинг другой странички.

Т.е. есть ряд задач, в которых независимое параллельное исполнение разных фрагментов кода -- это обыденная необходимость. И если код вынужден делать блокирующие вызовы, то помещение такого кода в отдельную нить является вполне нормальным и очевидным подходом. Тут как раз больше приключений можно себе создать пытаясь делать все это в одном рабочем потоке (как это было в эпоху однопоточных приложений в 16-битовых версиях Windows).

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

И тут оказывается, что старый добрый механизм обмена сообщениями между потоками является чуть ли не самым простым и наименее опасным способом реализации взаимодействия параллельно работающих потоков. Напортачить с примитивами синхронизации уровня critical section/mutex/semaphore/condition/event, особенно когда счет рабочих потоков идет на десятки, -- нет ничего проще :) Тогда как с очередями сообщений, даже если их сотни, все не так страшно. Ну а если использовать очереди сообщений, то нет смысла писать такие очереди для каждого проекта заново. Можно же сделать какую-то библиотеку, в которой будет собрано несколько оттестированных и задокументированных реализаций. Вот и вырисовывается первый камень в фундаменте SObjectizer-а: набор готовых очередей сообщений "из коробки".

Как только появляются очереди, сразу же встает вопрос: а как с ними работать? Грубо говоря, нужно ли вручную записывать циклы с вызовами pop()-ов и wait()-ов для очередей или эту задачу так же можно переложить на плечи библиотеки?

Тут лично у меня всплывают воспоминания о том, как менялось программирование для Windows. Первоначально, когда инструментов вроде MFC, OWL или Qt не было, разработчик просто записывал в одном месте цикл выборки Windows-сообщений, а в других местах (в т.н. WndProc-ах) были сосредоточенны большущие switch-и, разбирающие полученные окном сообщения.

Картина была жуткой. Можно было наткнуться на программу, в которой WndProc со своим switch-ем занимала не одну тысячу строк. Более вменяемые программисты делали декомпозицию: в switch-е выполнялась только первичная распаковка параметров сообщений и вызывались конкретные функции, каждая из которых обрабатывала только свой тип сообщения. Очень сильно упростило этот подход появление заголовочного файла windowsx.h и описанных там message crackers-макросов.

Но появление библиотек вроде MFC изменило разработку под Windows очень сильно. Это реально было прорывом: вместо написания отдельного обработчика, а потом включение его вручную в соответствующий switch, достаточно было просто описать метод класса.

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

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

Диспетчер в SObjectizer отвечает за создание и удаление рабочих потоков. Рабочий поток выполняет выборку сообщений из очередей и вызов соответствующих обработчиков у агентов. Соответственно, все, что нужно пользователю, -- это привязать своих агентов к тем или иным диспетчерам. Ну а так как в разных задачах есть необходимость в разных рабочих потоках (и разном их количестве), то в SObjectizer реализовано несколько типов диспетчеров (в SO-5.5.8 таковых будет цельных восемь штук) и есть возможность создавать столько диспетчеров, сколько потребуется пользователю.

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

Дабы у читателей не возникло подозрения, что это я такой умный, особо подчеркну, что все эти идеи были кристаллизованы тем уникальным коллективом лаборатории АСУТП и ИИС в Гомельском КБ СП, в котором мне повезло оказаться в середине 90-х.

Тем не менее, в сухом остатке: SObjectizer -- это инструмент для управления рабочим контекстом с готовыми средствами передачи информации между рабочими контекстами посредством сообщений.

Легко увидеть, что здесь нет ни слова про производительность. Имхо, SObjectizer не для того, чтобы выжимать все такты из вычислительных ядер. Если вам нужна максимальная производительность, то следует смотреть в другую сторону. Например, в сторону Threading Building Blocks. Ну а если хочется совсем странного -- и высокой производительности и, в то же время, модели акторов -- то в сторону C++ Actor Framework (Upd. В ряде случаев с SO-5 можно получить даже большую производительность, чем с CAF). Хотя, как по мне, так акторы и производительность это несколько противоречащие друг другу сущности. Ведь передача сообщений между нитями не бесплатна, а когда стоимость передачи начинает составлять проценты от стоимости обработки сообщения, то имеет смысл вообще отказаться от обмена сообщениями. Ну или же строить обмен сообщениями не на универсальных очередях, а на очень специализированных, заточенных под вашу задачу и, возможно, под особенности вашего оборудования. Но тут уж фреймворк общего назначения вряд ли будет полезен.

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

Ну а что это за задачи, в которых SObjectizer будет полезен? Сложно сказать :) Судя по дискуссиям на профильных форумах, сам по себе C++ уже мало где полезен :) Тем не менее, попробую поделиться своими мыслями.

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

Имитационное моделирование. Например, создание тестовых окружений, имитирующих сложные условия (разнообразные тестовые стенды). Здесь вообще акторы/агенты хорошо себя показывают, т.к. в акторе/агенте можно упрятывать логику имитации внешних сущностей (в том числе и внешнего оборудования).

Задачи, в которых требуется высокая производительность и, при этом, явно прослеживается стадийность обработки данных, где под каждую стадию обработки можно выделить нить или пул нитей. Например, перепаковка/преобразование/обработка графической/видео/аудио информации. Особенно, если над этим всем еще и GUI-морда, которая не должна тормозить, пока идет тяжелая фоновая работа. Тут саму вычислительную обработку можно оформить в виде группы агентов, каждый из которых занимается своей работой на своем собственном контексте.

Из этой же области и задачи обработки независимых потоков данных, особенно, если в процессе обработки нужно взаимодействовать с не очень предсказуемыми вещами (вроде СУБД или подключенного к компьютеру оборудования). Это как раз то, для чего моя команда применяла (и вроде как продолжает применять) SObjectizer в Интервэйле: потоки сообщений (как sms/ussd/email, так и финансовых транзакций) обрабатывались группами агентов, при этом каждая группа жила на своих собственных рабочих контекстах. Ну и, насколько я помню, стадийность обработки данных там так же присутствовала, что опять же легко реализовалось средствами SObjectizer.

В общем, я уверен в том, что задачи, подходящие для SObjectizer, есть. Их просто не может не быть ;) А вот будет ли там использоваться SObjectizer или нет... Тут у меня нет оснований преувеличивать степень своего влияния :) Максимум, что я могу сделать -- это продолжать развивать SObjectizer, улучшая его и в глубь, и в ширь :)

В связи с чем может возникнуть вопрос: а как долго все это продлится? Вопрос, прямо слово, не праздный. Особенно после недавних известий о закрытии biicode. Ведь никакой финансовой отдачи от разработки SObjectizer я не получаю. И, как мне думается, этот отдачи не предвидится в ближайшем обозримом будущем. Тому есть, как минимум, две причины. Во-первых, маятник OpenSource пока еще не качнулся в другую сторону. Т.е., народ уже привык пользоваться OpenSource на халяву и не шибко жаждет покупать платные инструменты разработки. Но при этом пока еще не столкнулся с тем, что без достаточного финансирования OpenSource-проекты со временем будут превращаться в унылое говно. Во-вторых, дела у C++ сейчас совсем не так хороши, как 20 лет назад. Нужно иметь очень и очень веские причины, чтобы начинать сейчас новые проекты на C++. Надеюсь, что после долгостроя под названием C++11, дела пойдут лучше. Но насколько лучше -- это нужно будет посмотреть.

Короче говоря, разработка SObjectizer идет и будет идти дальше на моем энтузиазме. И движитель очень простой: я люблю делать хорошие вещи. При этом не перфекционист, для меня достаточно уровня good enough. Просто этот enough находится где-то у верхней кромки моих возможностей :)

Так уж получилось, что работая в КБ СП, а затем в Интервэйле я имел возможность заниматься именно этим, т.е. делать хорошие вещи. Так, как умею и считаю нужным. Смею утверждать, что делал неплохо. Потом, в силу различных обстоятельств, этой возможности оказался лишен. А работа над SObjectizer ее мне вернула. Будет ли еще в жизни шанс заниматься тем, чем хочешь и в том режиме, который удобен тебе -- не знаю. Вполне возможно, что нет. Поэтому стараюсь использовать уже выпавший шанс по полной.

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

Если такие перспективы развития событий кого-то удерживают от начала использования SObjectizer (мол, за проектом стоит совсем маленькая команда, будущее проекта непонятно), то могу сказать, что зря. Во-первых, жизнь учит, что даже большие компании, случается, закрывают свои разработки, которые были предъявлены миру с большой помпой. Тогда как маленькие проекты живут десятилетиями и держатся на увлеченности их автора (недавнее 17-летие проекта curl тому хороший пример).

Во-вторых, SObjectizer -- это небольшой OpenSource проект. Даже если я перестану им заниматься, все исходники и документация останется. И мне не кажется, что в нем есть какой-то rocket science и что с его потрохами сложно будет разобраться. При том, что я уже давно не тот программист, которым был 10 лет назад, но все-таки надеюсь, что код в SO-5 нормальный.

Кроме того, на данный момент живых OpenSource фреймворков такого рода для C++ всего два: SObjectizer и C++ Actor Framework. Они довольно сильно отличаются по своим возможностям и направленности. И хотя в качестве английского языка в документации SObjectizer явно проигрывает CAF-у, в объеме этой самой документации, а так же в количестве примеров тут можно еще поспорить кто кого :) К тому же в релизах SObjectizer мы довольно трепетно относимся к сохранению совместимости между версиями. Поэтому переход на новые версии SObjectizer происходит относительно безболезненно даже при серьезных изменениях в версиях. Ну а после выхода версии 5.5.0 ломающих изменений не вносилось вообще, при этом сейчас к релизу готовится уже версия 5.5.8.

На мой взгляд, нынешний SObjectizer еще не дошел до того уровня, который я бы сам хотел видеть. Но по сравнению с прошлогодним релизом версии 5.4.0 (это как раз август 2014-го) теперешний SO-5.5.* -- это продукт уже совсем другого уровня зрелости.

Ну и, в-третьих, мало что так поддерживает энтузиазм и желание заниматься проектом дальше, как информация о том, что проект используется. Да и вообще любая обратная связь очень помогает, особенно, если это положительная обратная связь. Так что чем больше народу знает и использует SObjectizer, тем более светлое у проекта будущее.

В общем, резюмируя: SObjectizer жив и SObjectizer будет жить. Еще какое-то время ;)

Ну и отдельное спасибо тем терпеливым читателям, которые дошли до этой строки!

PS. В заголовок вынесен слоган, который с недавнего времени описывает SO-5 на SourceForge.

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