воскресенье, 30 января 2011 г.

Khameleon Modular System - возрождение

Давненько я ничего не писал про столь важный проект, как модульная система хамелеон. Ровно год, одна неделя и один день, если быть точным. А дело, тем временем, совсем не стояло на месте. Об этих переменах я сегодня и расскажу.

Не буду повторять проблемы, о которых я писал в предыдущем посте, а перейду сразу к решениям.
1. Взамен концепции конвейера пришла концепция взаимодействия модулей через т.н. пропагаторы. Термин пришел из ядерной физики, где он означает "переносчик взаимодействия", или, буквально, от английского "propagate" - распространение. Возможно термин притянут за уши, но лучшего определения той сущности, которую я хотел создать, не нашлось. Это не труба (pipe), из которой к вам сыпется некое нечто, и в которое вы ссыпаете всё скопом. Это не очередь запросов (queue) и не сигнал, в терминологии Qt. Основная идея пропагатора состоит в том, чтобы обеспечить максимально-абстрактный интерфейс взаимодействия модулей друг с другом. Пропагаторы разделены на два подкласса, feedback и stream, т.е. те, что ожидают ответа от модуля и возвращают его (подобно фукциям read/write в блокирующем режиме), и те, посредством которых можно организовать "массовую рассылку" или поток (подобно функциям send/received). Внутри модуля создаётся список пропагаторов, через которые происходит взаимодействие, а ядро при загрузке создаёт сами объекты, и связывает их с другими модулями. Т.о. каждый пропагатор может являться точкой входа в модуль. В примеру, модуль построения графиков multiplot имеет пропагаторы plotxy и collectxy. Первый отвечает за рисование графика целиком, второй - за накопление и отрисовку потока данных. Взаимодействие с пропагатором внутри модуля выглядит как обычный вызов функции.

2. Многопоточность. Так получилось, что реализация многопоточности оказалась тесно связанной с реализацией идеи о пропагаторах. Просто по тому, что сейчас feedback пропагатор реализован таким образом, что связывать им можно только модули, находящиеся в разных потоках, иначе будет deadlock.
Как это работает? При загрузке модуль может попросить (через параметр в конфиге) для своей работы отдельный поток. Ядро стартует новый тред со своим eventloop-ом, и помещает туда модуль, вызывая для него moveToThread(). Поскольку работа пропагаторов крутиться вокруг qt-ной концепции сигнал-слотов, то все вызовы между модулями из разных потоков полностью безопасны.

Казалось бы, зачем так всё усложнять, если требуется "просто" связать два модуля и сделать между ними практически синхронный обмен данными? На этот вопрос я подробно напишу в отдельной заметке. Несомненно, задачу можно было бы решить и в рамках одного потока, и без надуманных пропагаторов, и, возможно, более красиво. Однако, в свой подход я закладываю некоторые плюшки, реализация которых другими способами (имхо) будет уже совсем не тривиальной. Ну например, раздать нескольким модулям задания по обработке, запустить при этом чтение их третьего, после дождаться завершения обработки от первых, и выполнить какие либо действия по результатам их работы. и всё это в виде линейного кода.

На данный момент могу сказать, что с введением многопоточности и концепции пропагаторов жить стало заметно легче, и уже в ближайшее время я выложу кратенький обзор нескольких практически применимых творений. Это будет простая оболочка для снятия данных с измерителя мощности ThorLabs, и ModBus Debugger - приложение для отладки устройств по протоколу modbus (постоянный мониторинг регистров, флагов, запись в регистры).

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