Конечные автоматы - это инструмент для достижения определенной цели. Как и любой инструмент, ими тоже можно злоупотреблять.
Они не самый добрый из инструментов, но работу, в которой они хороши, невозможно выполнить другими средствами (и обычно любой другой подход обречен быть ужасным беспорядком в тысячу раз хуже, чем машина).
Работа выполняется в условиях, когда классические состояния ожидания запрещены.
Я должен прочитать сенсорный экран. Чтобы прочитать позицию, мне нужно обменяться примерно 15 командами через SPI. Мне нужны хорошие 100 показаний в секунду. Мне нужно подождать около 1 микросекунды после каждой команды, чтобы соответствующий флаг занятости исчез, прежде чем я смогу продолжить. Существует также ряд других операций, которые должны быть доступны через тот же интерфейс, таких как настройка контрастности, изменение режимов, включение или выключение подсветки, считывание температуры. Если бы я выполнял while(BUSY_BIT);
за каждое ожидание, я бы съел весь процессор за считанные минуты. Если бы я сделал sched_yield()
или usleep(1)
, я бы никогда не достиг желаемого количества показаний. Единственный путь - это конечный автомат.
Но есть способы сделать так, чтобы конечный автомат тоже играл хорошо. Скройте машину за кулисами и предоставьте разработчикам функции для работы.
До сих пор в моем опыте работы преобладали 2 системы, основанные на 3 разных конечных автоматах.
- большой веб-портал, где на каждом шаге вы извлекаете некоторые данные из базы данных и на ее основе готовите больше запросов. На последнем шаге вы используете данные для генерации HTML. Каждая задача - модуль веб-страницы - была реализована как класс PHP, унаследованный от движка. Состояние было сохранено в переменных класса. Каждый шаг был отдельной функцией. В конце шага хранимые запросы были оптимизированы и отправлены в механизм через кэши, а ответы были возвращены к оригиналу.
- встроенное устройство со многими подсистемами. Task Pump используется. Каждый модуль регистрирует обработчик, который вызывается много раз в секунду из основного цикла. Обработчик может сохранять состояние в статических или классовых переменных с состояниями. Эта совместная многозадачность позволяет значительно уменьшить объем занимаемой памяти, чем запуск всех в отдельных потоках, позволяет вручную назначать приоритеты задачам, регистрируя их дважды, и запускать поток с высоким приоритетом, перекрывая остальную часть системы.
- пол-переводчик. Этот сенсорный экран. Вызовы функций и их состояния ожидания регистрируются, но каждый из них вызывается только один раз, а затем удаляется из очереди программы. Интерпретатор вызывается как задача taskpump, выполняющего ограниченное количество функций до тех пор, пока не встретит функцию, помеченную как состояние ожидания (или превышающую количество вызываемых функций). Затем оно продолжается, пока состояние ожидания не исчезнет. Другие задачи ставят в очередь задания как (иногда длинные) последовательности выполняемых функций, а затем ждут результата. Таким образом, я могу ограничить количество состояний, которые мне нужно создать, до 4, где мне нужны результаты. Если команда «отправить, никогда не проверять результат», например «установить контраст», им вообще не нужны дискретные состояния. Таким образом, фактическими состояниями являются «ожидание события и регистрация запрошенных данных», «ожидание измерения» и «считывание результатов и их правильное назначение».
Код будет в два раза проще и в три раза яснее, если будет написан структурно или последовательно. За исключением того, что это не будет работать, или будет работать с ужасной производительностью.