【问题标题】:Implementing a finite state machine with a single coroutine使用单个协程实现有限状态机
【发布时间】:2014-01-09 05:02:41
【问题描述】:

我正在寻找实现 FSM 的方法,这导致我第一次遇到协程。

我看到了几个示例(hereherehere),它们暗示状态机可以由单个协程实现。然而,我注意到所有这些机器的共同点是,不包括循环,它们是树——也就是说,从起始节点到每个其他节点只有一条路径(不包括循环)——并且很好地映射到层次控制由嵌套的ifs 提供的流。我试图建模的状态机至少有一个状态,从起始节点到它的路径不止一条(如果消除了循环,它就是一个有向无环图)。而且我无法想象什么样的控制流(除了gotos)可以实现这一点,或者是否有可能。

或者,我可以使用单独的协程来处理每个状态并让步给某种调度程序协程。但是,我认为在此设置中使用协程相对于常规函数没有任何特别的好处。

这是一个我无法建模的简单状态机:

A --'a'--> B
A --'b'--> C
B --'a'--> C
B --'b'--> A
C --'a'--> A
C --'b'--> B

这就是我目前所拥有的。最终实现将在 C++ 中使用 Boost,但我使用 Python 进行原型设计。

#!/usr/bin/python3

def StateMachine():
    while True:
        print("  Entered state A")
        input = (yield)
        if input == "a":
            print("  Entered state B")
            input = (yield)
            if input == "a":
                # How to enter state C from here?
                pass
            elif input == "b":
                continue
        elif input == "b":
            print("  Entered state C")
            input = (yield)
            if input == "b":
                continue
            elif input == "a":
                # How to enter state B from here?
                pass

if __name__ == "__main__":
    sm = StateMachine()
    sm.__next__()
    while True:
        for line in input():
        sm.send(line)

能否修复这个协程以正确建模状态机?还是我必须采取其他方式?

【问题讨论】:

    标签: coroutine fsm state-machine


    【解决方案1】:

    理论上,每个有限状态机都可以用一个协程来实现,只需要以下结构之一:

    • 辅助变量测试
    • goto 声明
    • 循环中的多级退出

    当然,if-then-elsewhile 结构被认为是可用的。另一方面,只有单级出口,这样的实现并不总是可行的。

    于是,具体的例子就这么写了,没有状态变量和goto的,如下:

    #!/usr/bin/python3 
    # Python doesn't support multilevel exits, so try/except can be used instead.
    # However, for this FSM, single-level exits are sufficient.  
    def StateMachine():
      while True:
        while True:
          print("  Entered state A")
          input = (yield)
          if input == "a":
              print("  Entered state B")
              input = (yield)
              if input == "a": 
                break
              elif input == "b": 
                pass
          elif input == "b": 
                break    
        while True:
          print("  Entered state C")
          input = (yield)
          if input == "a": 
              break
          elif input == "b": 
              print("  Entered state B")
              input = (yield)
              if input == "a": 
                pass
              elif input == "b": 
                break
    if __name__=="__main__":
         sm = StateMachine()
         sm.__next__()
         while True:
           for line in input():
            sm.send(line)
    

    【讨论】:

      【解决方案2】:

      我会明确地为状态机建模:

      def StateMachine():
          state = 'A'
          while True:
              print(state)
              input = (yield)
              if state == 'A':
                  if input == 'a':
                      state = 'B'
                  elif input == 'b':
                      state = 'C'
                  else:
                      break
              elif state == 'B':
                  if input == 'a':
                      state = 'C'
                  elif input == 'b':
                      state = 'A'
                  else:
                      break
              elif state == 'C':
                  if input == 'a':
                      state = 'A'
                  elif input == 'b':
                      state = 'B'
                  else:
                      break
              else:
                  break
      

      这应该使用嵌套的switch 语句或状态转换表非常巧妙地转换为 C++。

      如果您更喜欢隐式模型,则需要一种方法来处理状态机中的循环。在 C 或 C++ 中,这可能最终是 goto。我不推荐这种方法,但如果你更喜欢它而不是显式状态,它可能看起来像这样:

      #include <stdio.h>
      
      #define start(state) switch(state) { case 0:;
      #define finish default:;}
      #define yield(state, value) do { state = __LINE__; return (value); case __LINE__:; } while(0)
      
      struct coroutine {
          int state;
      };
      
      int
      run(struct coroutine *c, char input)
      {
          start(c->state)
          {
      A:
              printf("Entered state A\n");
              yield(c->state, 1);
              if(input == 'a') goto B;
              if(input == 'b') goto C;
      B:
              printf("Entered state B\n");
              yield(c->state, 1);
              if(input == 'a') goto C;
              if(input == 'b') goto A;
      C:
              printf("Entered state C\n");
              yield(c->state, 1);
              if(input == 'a') goto A;
              if(input == 'b') goto B;
          } finish;
      
          return 0;
      }
      
      int
      main(void)
      {
          int a;
          struct coroutine c = {0};
      
          while((a = getchar()) != EOF && run(&c, a));
      
          return 0;
      }
      

      【讨论】:

      • 拥有明确的状态始终是一种选择,但这是我试图避免的事情。
      • 我认为您将遇到的问题是您试图将循环图表示为一棵树。这只有在您允许重新进入树的情况下才有可能;这将把goto 之类的东西带到树的中间,或者用一个变量来保存状态,以便可以在顶部重新进入树。
      • 我自己也这么认为,但我找不到“仅使用协程不能用于实现循环有限状态机”的参考。
      • 它并不是真正特定于协程的。您希望将每个状态转换实现为自己的 if 分支,但您需要跳过树的分支。 Python 没有goto,但 C 和 C++ 有,我将编辑我的答案以在 C 中包含一个带有gotos 的示例。我认为这不是最好的方法,但它确实展示了可能性。
      • 另外一个我想避免提及的附加说明是因为害怕混淆水域:协程或生成器本身通常在幕后作为状态机实现(其中状态是行或指令在下一个简历上执行)。我觉得我至少必须指出这一点,因为 C 中确实没有任何“幕后”。这就是那些 #define 宏的真正意义所在。
      猜你喜欢
      • 2012-06-26
      • 2011-03-31
      • 1970-01-01
      • 1970-01-01
      • 2013-07-19
      • 2020-11-18
      • 2018-11-13
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多