большие и вложенные конечные автоматы

У меня есть конечный автомат в системе реального времени с очень немногими (3) состояниями.

typedef enum {
    STATE1,
    STATE2,
    STATE3
} state_t;

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

typedef enum {
    STATE1,
    STATE1_PREPARE_TRANSITION_TO_STATE2,
    STATE1_DO_TRANSITION_TO_STATE2,
    STATE1_PREPARE_TRANSITION_TO_STATE3,
    STATE1_DO_TRANSITION_TO_STATE3,
    STATE2,
    ...
} state_t;

или я создаю вложенный конечный автомат для соответствующих основных состояний:

typedef enum {
    STATE1_NOT_ACTIVE,
    STATE1_NORMAL,
    STATE1_PREPARE_TRANSITION_TO_STATE2,
    STATE1_DO_TRANSITION_TO_STATE2,
    STATE1_PREPARE_TRANSITION_TO_STATE3,
    STATE1_DO_TRANSITION_TO_STATE3
} sub_state1_t;
...

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

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

if ((global_state == STATE1) &&
    (sub_state_1 == STATE1_DO_TRANSITION_TO_STATE2))
{
    ...
    if (transition_xy_done(...))
    {
        global_state = STATE2;
        sub_state_1 = STATE1_NOT_ACTIVE;
        sub_state_2 = STATE2_NORMAL;
    }
}

Каков общий наилучший подход к такой проблеме: много маленьких и вложенных конечных автоматов (со многими недопустимыми комбинациями), один большой конечный автомат или что-то еще?


person groovingandi    schedule 10.09.2009    source источник
comment
Рассматривали ли вы использование некоторого генератора конечного автомата?   -  person Hasturkun    schedule 10.09.2009
comment
Есть ли какой-нибудь инструмент-генератор, который может решить, что лучше: один большой или несколько вложенных конечных автоматов?   -  person groovingandi    schedule 10.09.2009
comment
Вероятно, нет, но генератор может упростить разработку/поддержку большого конечного автомата.   -  person Hasturkun    schedule 10.09.2009


Ответы (6)


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

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

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

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

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

person Michael Burr    schedule 10.09.2009

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

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

person G Gordon Worley III    schedule 10.09.2009

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

Еще один недостаток большого SM - большая таблица переходов, поэтому поиск занимает больше времени.

person qrdl    schedule 10.09.2009

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

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

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

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

Чтобы привести упрощенный пример:

#define MyDataSIZE 10

void UpdateStateMachine(void)
{
    static enum {BeginSTATE, DoStuffSTATE, EndSTATE} State = BeginSTATE;
    static unsigned int Counter = 0;
    static unsigned int MyData[MyDataSIZE];

    switch(State)
    {
        default:
        case BeginSTATE:
            /* Some code */
            if(/* Some condition*/)
                {State = DoStuffSTATE;}
            break;
        case DoStuffSTATE:
            /* Some actions on MyData[Counter] */
            if(/* Some condition*/)
                {State = EndSTATE;}
            break;
        case EndSTATE:
            /* Some code */
            if(/* Some condition*/)
            {
                Counter++;
                if(Counter >= MyDataSIZE)
                    {Counter = 0;}
                State = BeginSTATE;
            } /* if */
            break;
    } /* switch */
} /* UpdateStateMachine() */
person Steve Melnikoff    schedule 12.09.2009

Почему бы вам не использовать шаблон состояния?

person Wouter    schedule 10.09.2009
comment
Возможно, я что-то упустил, но я не думаю, что это имеет значение, потому что я ограничен C и у меня нет одинаковых подсостояний для каждого основного состояния. - person groovingandi; 10.09.2009
comment
Извините, не увидел тег C. Я предположил, что это С++. - person Wouter; 10.09.2009

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

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

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

Также, как вы предложили, работа с несколькими конечными автоматами заставит вас отправлять больше параметров, выполнять более одного теста для каждого состояния и т. д. '...

Что касается будущих ожиданий, я верю в YAGNI.

person Liran Orevi    schedule 13.09.2009