Асинхронный Конечный Автомат: Идеология И Технология



Введение Хорошо, когда твои подчиненные никогда не болеют, не умирают, всегда присутствуют на работе и выполняют твои приказы без предварительной подготовки: «Вы позвонили, вставайте».

Это, например, веб-сервисы, которые следуют модели REST (которая, если отбросить специальную HTTP-терминологию, сводится к тому, что интерфейс сервиса на самом деле является интерфейсом контейнера данных).

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

Асинхронный конечный автомат — это удобная абстракция верхнего уровня для управления сущностями с богатым и не всегда предсказуемым внутренним миром.

Этот объект может быть аппаратным устройством, сеансом сетевого протокола или просто одновременно выполняемым процессом, код которого вы не контролируете.

Описанная ниже архитектура асинхронного конечного автомата решает ряд стандартных задач, возникающих при «фронтальной» интеграции подсистем с учетом их внутреннего состояния.

Самая заметная из этих проблем — недостаточное разделение (я бы даже сказал недостаточная «гальваническая развязка») сигнальных сущностей и переход между состояниями, из-за чего машина становится неустойчивой к DoS-атакам.

Есть и другие, менее очевидные — например, «недостаточно атомарная» замена узла подсистемы или используемого им ресурса.



Анатомия (разложение объектов)

Модель конечного автомата включает в себя следующие основные объекты:
  1. Состояние – это режим работы управляемой системы, отличающийся от других предоставляемыми возможностями.

    Таким образом, в понятие «состояние» не входят снимки кэшей и буферов, варианты циклов «от приема до обеда» и другие аварии управляемой системы.

    Обычно штатов должно быть всего несколько; если счет переходит во второй десяток, скорее всего, управляемая система должна быть фрагментированной или иерархизированной.

  2. Условие — это логическое значение (истина или ложь) на одном из «входов» системы.

    Суперпозиция состояний всех входов автомата однозначно определяет целевое состояние автомата.

    Таким образом, любой входной сигнал, значимый для состояния машины, в конечном итоге сводится к заданию значения одного или нескольких условий.

  3. Реакция — это реакция машины на разницу между текущим состоянием и целевым состоянием.

    Мы насчитали два с половиной разных типа реакций: прямой переход между состояниями, маршрут и остановка маршрута («кирпичик»).

    Прямая ветвь также может быть нулевой операцией (NOP) — например, если изменение входных данных вызвано уведомлением о завершении асинхронной операции.

Снова: состояние определяется набором условий, реакция определяется парой состояний .

Любой набор входных данных может быть связан с некоторым состоянием, то есть ни для одного набора входных данных желаемое состояние системы не должно быть неопределенным.

Маршруты задаются лениво — для каждой пары состояний, между которыми не указан прямой переход, указывается промежуточное целевое состояние.

Он похож на шлюз в маршрутизации сетевых пакетов — с одним отличием: маршруты могут быть вложенными.

Желаемое состояние пересчитывается после каждого выполненного перехода.

Таким образом, нет никакой гарантии, что маршрут будет пройден до конца.

Это уменьшает «мечтательность» конечного автомата, то есть сокращает время реакции на неожиданное изменение ситуации.

«Стоп-маршрут» — это маршрут, на котором начальное и промежуточное состояния совпадают. Он используется для обозначения пар состояний (фактического и желаемого), в которых ничего не следует делать, а следует подождать, пока желаемое состояние не изменится.

Если вы пишете на Java, определите наборы условий и состояний как перечисления, а в абстрактной реализации автомата используйте параметризацию EnumSet для «слова состояния» и EnumMap для таблиц маршрутов и переходов.

Это не догма.

Наверное, можно сделать как-то иначе.

Но, как говорит армянское радио, очень жаль.



Физиология (потоки и передача сигналов)

Удобно строить асинхронный конечный автомат над очередью сообщений.

Android (пакет android.os) имеет фреймворк MessageQueue + Looper + Handler, который идеально соответствует нашим требованиям; во «взрослой» Java вы можете использовать ThreadPoolExecutor из одного потока или просто цикл, анализирующий LinkedBlockingQueue. Если у вас нет ограничений по блокирующим ресурсам, не экономьте на заваривании и выделяйте поток для каждой управляемой системы.

Ответные сообщения клиентскому коду (по умолчанию это идентификаторы состояний, в которые переходит управляемая система, но могут быть и полезные результаты — молоко, телята.

) отправляются через потокобезопасный список подписчиков.

(В C# подписка делегатов на события реализована «из коробки».

) В методе отправки сообщения удобно возвращать логический флаг «Мне достаточно».

Самое простое, что можно сделать с таким флагом — это реализовать одноразовую «защелку», при которой клиент может ждать нужного ему состояния; но можно написать и достаточно сложный сценарий («после А делай то, после Б делай то и отцепляйся»).

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

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

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

Полная асинхронность исключает цикл из обработчика исчерпания очереди, но требует явного «пинга» (отправки пустого сообщения) в случае, если состояние системы изменилось (т. е.

мы не остановились в цикле остановки маршрута), но маршрут был не пройден до конца.

Можно обойтись и без пингов, организовав цикл с условием «нужное состояние не равно фактическому, и при этом мы не попали на стоп-маршрут».

В обоих случаях маршрут остановки должен быть четко обозначен.



Педагогика (уровни запуска и паника)

Понятная таблица состояний составляется на основе последовательно применяемого метода containsAll() — то есть система считается находящейся в состоянии A(n), если все необходимые условия для этого состояния присутствуют в «слове состояния» входные данные, и то же самое верно не для всех A (m|mЭэкономика (ресурсы) Ресурсы, которые вы добываете самостоятельно, не требуют особого обращения.

В частности, их можно хранить в обычных нефинальных энергонезависимых приватных полях.

Информация о том, есть ли у вас этот ресурс, полностью зависит от вашего текущего состояния.

Ресурс, требующий особого обращения, — это зависимость: то, что управляемая система использует, не производя этого сама.

Ресурсы берутся извне.

Внедрение зависимостей в конечный автомат реализуется отдельным паттерном, который мне нравится называть «оборудованием».

Каждый «слот» оборудования имеет один постоянный атрибут: состояние, сигнализирующее о доступности данного типа оборудования — и два значения: рекомендуемое и эффективное.

Предложено — это то, что предлагает клиент. Эффективно то, что реально используется.

Состояние оборудования проверяется в начале обработчика исчерпания очереди и после каждого перехода состояния.

Проверка состояния оборудования производится следующим образом (разбираем наиболее сложный случай, когда ненулевое эффективное заменяется ненулевым предложенным - остальные получаются удалением неактуальных «если-то»):

  • Если предложено != эффективное и эффективное != ноль, то флаг «доступное оборудование» сбрасывается.

  • Если в текущем состоянии оборудование «требуется» (т.е.

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

  • Если старое (предлагаемое != эффективное) оборудование в текущем состоянии не требуется, мы сбрасываем его в ноль.

  • Если в текущем состоянии предложено != ноль && эффективное == ноль, то присвойте эффективное = предложено и установите флаг доступности оборудования.

Метод get() объекта держателя оборудования является пакетно-частным и возвращает эффективность.

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

(С другой стороны, заказы клиентов доставляются только активному подрядчику.

)

Этика и психология семейной жизни (интеграция)

Строгое разделение входных сигналов в зависимости от их происхождения:
  • инструкции из клиентского кода («поехали!»);
  • обратные вызовы от управляемой системы («стучит левый двигатель»);
  • состояние ресурсов («в замке находится ключ зажигания»).

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

Логическое смешивание (сложение, умножение) этих условий происходит уже в редукторе «слова входов» к самому состоянию.



Гигиена (лучшие практики)

Принципиально важно, чтобы все манипуляции с подсистемой выполнялись в порядке перехода между состояниями.

Строго все.

Настолько строгие, что в идеале в классе конкретной реализации автомата не должно быть никаких отдельных приватных методов — только делегаты (в смысле C#; в Java — псевдо-делегаты, такие как Callable или Runnable) в таблице переходов .

В какой-то степени ради этого можно даже терпеть дублирование кода — поскольку стандартные методы его устранения не устраняют порождаемую им избыточность семантики.

(Групповое заполнение одним делегатом нескольких ячеек таблицы переходов не приводит к размытию семантики и в целом кошерно; в нашей скелетной реализации есть такие облегченные методы.

) «Хуки» типа onStateEnter() или onStateExit() соответственно также не должны существовать.

Им придется либо знать, в каком «направлении» подсистема передает соответствующее им состояние, либо игнорировать его.

Убедитесь, что управляемая система («физика», остающаяся за фасадом вашего конечного автомата) не проявляет глупой инициативы — то есть не следит за своим состоянием сама (кроме диагностики ошибок) и не делает ничего «на месте».

в то же время» или «на всякий случай».

Сюда входит «ленивая инициализация» и попытки предсказать и упредить поведение клиента.

Все это является источником ошибок.

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

При автоматическом тестировании машины (извините за неуместный самоповтор) «загрузите» ее сообщениями с порядком изменения частоты во время теста: очень быстро (заметно быстрее, чем они обрабатывались бы по отдельности — чтобы убедиться в заполнении очереди ничему как таковому не угрожает), просто быстро (с частотой, сравнимой с частотой реакций - для проверки состояния гонки), медленнее частоты реакций (чтобы наблюдать самостоятельный "рост" уровня запуска по мере работы "физики") асинхронные задачи, и машина принимает независимые решения о последующих действиях).

P.S. Прежде чем подписывать клиента на изменения параметра А, синхронно передайте ему текущее значение А.

(«Будьте расово корректны – предотвратите состояние гонки»).



Заключение

Я написал какую-то мелочь, но судя по коду, который мне регулярно приходится читать, наверняка мелочь оказалась полезной.

Теги: #fsm #конечные автоматы #автоматы #драйверы #HAL #сетевые протоколы #очередь сообщений #асинхронная модель #DOS #переполнение очереди #защита от дурака #проектирование и рефакторинг #Алгоритмы

Вместе с данным постом часто просматривают:

Автор Статьи


Зарегистрирован: 2019-12-10 15:07:06
Баллов опыта: 0
Всего постов на сайте: 0
Всего комментарий на сайте: 0
Dima Manisha

Dima Manisha

Эксперт Wmlog. Профессиональный веб-мастер, SEO-специалист, дизайнер, маркетолог и интернет-предприниматель.