代码之家  ›  专栏  ›  技术社区  ›  groovingandi

大型与嵌套状态机

  •  3
  • groovingandi  · 技术社区  · 15 年前

    我在实时系统中有一个状态机,只有很少的(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;
        }
    }
    

    对于这样的问题,一般的最佳方法是什么:许多小型和嵌套的状态机(具有许多无效组合)、一个大型状态机或其他什么?

    6 回复  |  直到 15 年前
        1
  •  4
  •   Michael Burr    15 年前

    首先,我要赞扬您认识到正在发生的事情,并使这些状态显式化(因为它们实际上是您的模型中的附加状态,不是真正的动作转换)。我经常看到状态机最终像您的最后一个例子(您希望避免的)一样结束。当您在事件处理程序中测试“附加”状态变量时,这表明您的状态机具有更多的状态,这些状态实际上已经投入到了设计中——这些状态将反映在设计中,而不是通过一系列用意大利面条编码的检查来干扰现有状态的事件处理程序,以获取用全局变量编码的附加“状态”。易物。

    C++有几种框架模型,它们是分层状态机——HSMs -(这是你嵌套状态机的想法听起来),但是我知道的唯一支持直C的是 Quantum Framework 我认为,接受这一点可能意味着一个相当程度的承诺(也就是说,这可能不是一个简单的改变)。但是,如果你不想研究这种可能性,Samek写了很多文章( and a book )关于如何在C中支持HSMS。

    但是,如果您不需要HSM模型中一些更复杂的部分(例如,不由“最内部”状态处理的事件会冒泡到可能由父状态处理,对整个状态层次结构的完全进入和退出支持),那么很容易支持嵌套状态机,就像完全独立于当进入/退出父状态时,自动启动和停止机器。

    大状态机模型可能更容易实现(它只是您现有框架中的多个状态)。我建议,如果将状态添加到当前的状态机模式并不会使模型太复杂,那么只需继续。

    换句话说,让最适合你的 模型 驱动如何在软件中实现状态机。

        2
  •  5
  •   Gordon Seidoh Worley    15 年前

    许多小型的状态机会给您带来更多的代码灵活性,特别是当您需要重新设计任何东西时。然后,您应该(希望)能够更改嵌套状态机,而不必更改任何其他嵌套状态机。

    拥有一个更大的转换表不会导致更长的查找时间,因为我假设您在内存中明智地布局了表。如果有什么不同的话,你应该能够从大型机器中获得更高的速度,仅仅是因为你没有额外的一个或两个步骤来让小型机器在它们之间进行干净的转换。但是考虑到这个方法的复杂性,我建议如下:使用嵌套状态机进行设计,然后一旦一切正常,如果需要的话,重构成一个单一状态机,以获得一点速度提升。

        3
  •  1
  •   qrdl    15 年前

    正如您所提到的,大型状态机变得混乱,所以很难维护。几个较小的短信息总是更容易理解和维护。

    另一个缺点是大的sm-更大的转换表,因此查找需要更长的时间。

        4
  •  1
  •   Steve Melnikoff    15 年前

    我不认为有一个单一的,一般的方法。正如其他人所说,这取决于你想做什么。

    一般来说,我会避免将小型状态机嵌套在大型状态机中,因为您不仅添加了更多的状态(因此也增加了复杂性),当您试图简化事情时,现在有两个状态变量可以跟踪。

    尤其是,当遍历“外部”状态机中的状态时,必须正确初始化“内部”状态变量。例如,如果由于一个bug,外部状态机中有一个转换,它无法重置内部状态机的状态变量,该怎么办?

    一个可能的例外是所有的内部状态机都做同样的事情。如果可以对数据进行参数化(例如,使用数组),那么您可以使用内部状态机的单个实现,并且可以用计数器或类似的东西替换外部状态机。

    举一个简单的例子:

    #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() */
    
        5
  •  0
  •   Wouter    15 年前

    为什么不使用 state pattern ?

        6
  •  0
  •   Liran Orevi    15 年前

    我投票支持更大的状态机,假设一台机器只能处于一个大的状态机状态,那么它在逻辑上应该存在。

    通过使用一台大型机器,您可以使用环境特性来防止同时存在两种状态的状态,从而使程序更安全、更可读。

    另外,一个大状态机的优点是,任何其他程序员都可以通过在一个地方查看(也就是说,了解全局)来轻松理解所有状态,而不是在一个地方查看,希望知道子划分,然后必须查看每个子划分。

    另外,正如您所建议的那样,使用多个状态机将强制您发送更多参数,对每个状态执行多个测试,等等……

    至于未来的期望,我相信 YAGNI .