1、FSM简介

1.1 有限状态机的定义

有限状态机(Finite State Machine,FSM)是一种数学模型,用于描述系统在不同状态下的行为和转移条件。

状态机有三个组成部分:状态(State)、事件(Event)、动作(Action),事件(转移条件)触发状态的转移和动作的执行。动作的执行不是必须的,可以只转移状态,不指定任何动作。总体而言,状态机是一种用以表示有限个状态以及这些状态之间的转移和动作的执行等行为的数学模型。

状态机可以用公式 State(S) , Event(E) -> Actions (A), State(S’)表示,即在处于状态S的情况下,接收到了事件E,使得状态转移到了S’,同时伴随着动作A的执行。

Event(事件)是指触发状态转换的输入信号或条件。它可以是任何类型的输入,例如传感器数据、用户输入、网络消息等。在编程中,Event通常是一个枚举类型,每个枚举值代表一个特定的事件。

State(状态)是指系统在某一时刻所处的状态,它是系统的一种抽象描述。在有限状态机中,状态是由一组状态变量来描述的,这些状态变量的取值决定了系统的状态。状态可以是离散的,也可以是连续的。在有限状态机中,状态通常用一个圆圈来表示,圆圈内部写上状态的名称。例如,一个简单的有限状态机可以有两个状态:开和关,它们可以用以下方式表示:

Action(动作)是指在状态转移时执行的操作或动作。当有限状态机从一个状态转移到另一个状态时,可以执行一个或多个action来改变系统的状态或执行某些操作。例如,当有限状态机从“待机”状态转移到“运行”状态时,可以执行一个action来启动系统。在实际应用中,action可以是任何有效的代码,例如函数调用、变量赋值、打印输出等。

FSM 通常用于编程中,用于实现状态转移和控制流程。

注意:

在任何时刻,FSM 只能处于一种状态。

1.2 Go中的FSM

通过上面关于有限状态机的定义,我们大概知道了状态机是个什么东西,那么Golang中是怎么实现的呢。不用慌,已经有大佬实现好了,只管用就好了。

安装:

go get github.com/looplab/fsm@v1.0.1

接下来一起看看github.com/looplab/fsm 是如何使用的。

2、github.com/looplab/fsm 如何使用

注意:

不同版本的 fsm 使用方式,可能不太一样,最好是看下 NewFSM 函数的注释,看下具体的细节。 本篇文章以:github.com/looplab/fsm@v1.0.1 为例。

2.1 fsm 基础使用

这里把官方的例子改了下,感觉官方的例子不是很清晰。代码如下:

package main

import (
"context"
"fmt" "github.com/looplab/fsm"
) type Door struct {
Name string
FSM *fsm.FSM
} func NewDoor(name string) *Door {
d := &Door{
Name: name,
} d.FSM = fsm.NewFSM(
"closed",
fsm.Events{
{Name: "open", Src: []string{"closed"}, Dst: "open"},
{Name: "close", Src: []string{"open"}, Dst: "closed"},
},
fsm.Callbacks{
"enter_state": func(_ context.Context, e *fsm.Event) { d.enterState(e) },
},
) return d
} func (d *Door) enterState(e *fsm.Event) {
fmt.Printf("The door's name:%s , current state:%s\n", d.Name, e.Dst)
} func main() {
door := NewDoor("测试") fmt.Printf("fsm current state: %s \n", door.FSM.Current()) err := door.FSM.Event(context.Background(), "open")
if err != nil {
fmt.Println(err)
}
fmt.Printf("fsm current state: %s \n", door.FSM.Current()) err = door.FSM.Event(context.Background(), "close")
if err != nil {
fmt.Println(err)
}
fmt.Printf("fsm current state: %s \n", door.FSM.Current())
}

执行结果:

fsm current state: closed
The door's name:测试 , current state:open
fsm current state: open
The door's name:测试 , current state:closed
fsm current state: closed

这里就通过Event改变FSM中的状态。转移公式为:Src,Event -> Dst,d.enterState。大意就是接受到了输入Event,状态机的StateSrc->Dst,并且执行了Action:d.enterState。

2.2 fsm 中 Action 何时执行?

刚开始使用的时候,好奇d.enterState(e)是什么时候调用的,我们一起看看 NewFSM 中的注释就清楚了。

// NewFSM constructs a FSM from events and callbacks.
//
// The events and transitions are specified as a slice of Event structs
// specified as Events. Each Event is mapped to one or more internal
// transitions from Event.Src to Event.Dst.
// Callbacks are added as a map specified as Callbacks where the key is parsed
// as the callback event as follows, and called in the same order:
//
// 1. before_<EVENT> - called before event named <EVENT>
//
// 2. before_event - called before all events
//
// 3. leave_<OLD_STATE> - called before leaving <OLD_STATE>
//
// 4. leave_state - called before leaving all states
//
// 5. enter_<NEW_STATE> - called after entering <NEW_STATE>
//
// 6. enter_state - called after entering all states
//
// 7. after_<EVENT> - called after event named <EVENT>
//
// 8. after_event - called after all events
//
// There are also two short form versions for the most commonly used callbacks.
// They are simply the name of the event or state:
//
// 1. <NEW_STATE> - called after entering <NEW_STATE>
//
// 2. <EVENT> - called after event named <EVENT>
//
// If both a shorthand version and a full version is specified it is undefined
// which version of the callback will end up in the internal map. This is due
// to the pseudo random nature of Go maps. No checking for multiple keys is
// currently performed.

从上面我们知道了,d.enterState(e) 是在called after entering all states 时执行的。

2.2.1 完整版书写的Callbacks执行顺序

从上面的注释能知道完整版书写的Callbacks的执行顺序如下:

2.2.2 简写版的Callbacks执行顺序

2.2.3 注意事项

虽然Callbacks的写法有两种,但是不能同时使用完整版和简写版,否则最终使用那个版本是不确定的。

2.3 较为完整的例子

package main

import (
"context"
"fmt" "github.com/looplab/fsm"
) type Door struct {
Name string
FSM *fsm.FSM
} func NewDoor(name string) *Door {
d := &Door{
Name: name,
} d.FSM = fsm.NewFSM(
"closed",
fsm.Events{
{Name: "open", Src: []string{"closed"}, Dst: "open"},
{Name: "close", Src: []string{"open"}, Dst: "closed"},
},
fsm.Callbacks{
"before_open": func(_ context.Context, e *fsm.Event) { d.beforeOpen(e) },
"before_event": func(_ context.Context, e *fsm.Event) { d.beforeEvent(e) },
"leave_closed": func(_ context.Context, e *fsm.Event) { d.leaveClosed(e) },
"leave_state": func(_ context.Context, e *fsm.Event) { d.leaveState(e) },
"enter_open": func(_ context.Context, e *fsm.Event) { d.enterOpen(e) },
"enter_state": func(_ context.Context, e *fsm.Event) { d.enterState(e) },
"after_open": func(_ context.Context, e *fsm.Event) { d.afterOpen(e) },
"after_event": func(_ context.Context, e *fsm.Event) { d.afterEvent(e) },
},
) return d
} func (d *Door) beforeOpen(e *fsm.Event) {
fmt.Printf("beforeOpen, current state:%s, Dst:%s \n", d.FSM.Current(), e.Dst)
} func (d *Door) beforeEvent(e *fsm.Event) {
fmt.Printf("beforeEvent, current state:%s, Dst:%s \n", d.FSM.Current(), e.Dst)
} func (d *Door) leaveClosed(e *fsm.Event) {
fmt.Printf("leaveClosed, current state:%s, Dst:%s \n", d.FSM.Current(), e.Dst)
} func (d *Door) leaveState(e *fsm.Event) {
fmt.Printf("leaveState, current state:%s, Dst:%s \n", d.FSM.Current(), e.Dst)
} func (d *Door) enterOpen(e *fsm.Event) {
fmt.Printf("enterOpen, current state:%s, Dst:%s \n", d.FSM.Current(), e.Dst)
} func (d *Door) enterState(e *fsm.Event) {
fmt.Printf("enterState, current state:%s, Dst:%s \n", d.FSM.Current(), e.Dst)
} func (d *Door) afterOpen(e *fsm.Event) {
fmt.Printf("afterOpen, current state:%s, Dst:%s \n", d.FSM.Current(), e.Dst)
} func (d *Door) afterEvent(e *fsm.Event) {
fmt.Printf("afterEvent, current state:%s, Dst:%s \n", d.FSM.Current(), e.Dst)
} func main() {
door := NewDoor("测试") fmt.Printf("fsm current state: %s \n", door.FSM.Current()) err := door.FSM.Event(context.Background(), "open")
if err != nil {
fmt.Println(err)
}
fmt.Printf("fsm current state: %s \n", door.FSM.Current()) err = door.FSM.Event(context.Background(), "close")
if err != nil {
fmt.Println(err)
}
fmt.Printf("fsm current state: %s \n", door.FSM.Current())
}

执行结果:大家重点看current state何时发生的变化。

fsm current state: closed
beforeOpen, current state:closed, Dst:open
beforeEvent, current state:closed, Dst:open
leaveClosed, current state:closed, Dst:open
leaveState, current state:closed, Dst:open
enterOpen, current state:open, Dst:open
enterState, current state:open, Dst:open
afterOpen, current state:open, Dst:open
afterEvent, current state:open, Dst:open
fsm current state: open
beforeEvent, current state:open, Dst:closed
leaveState, current state:open, Dst:closed
enterState, current state:closed, Dst:closed
afterEvent, current state:closed, Dst:closed
fsm current state: closed

参考资料:

looplab/fsm 源码阅读

有限状态机FSM

深入浅出理解有限状态机

[有限状态机](

Go中的有限状态机FSM的详细介绍的更多相关文章

  1. (转载)SQL语句中Group by语句的详细介绍

    转自:http://blog.163.com/yuer_d/blog/static/76761152201010203719835 SQL语句中Group by语句的详细介绍              ...

  2. QT中PRO文件写法的详细介绍

    学习Qt时,发现有些知识看了不经常用就忘了,以下是书本上写的一些关于qmake的相关知识,自己看后,打算把一些经常用到的记下来,整理整理. Qt程序一般使用Qt提供的qmake工具来编译. qmake ...

  3. 转载:Java中的字符串常量池详细介绍

    引用自:http://blog.csdn.net/langhong8/article/details/50938041 这篇文章主要介绍了Java中的字符串常量池详细介绍,JVM为了减少字符串对象的重 ...

  4. java中的compareto方法的详细介绍

    java中的compareto方法的详细介绍 Java Comparator接口实例讲解(抽象方法.常用静态/默认方法) 一.java中的compareto方法 1.返回参与比较的前后两个字符串的as ...

  5. Xcode中c++&Object-C混编,详细介绍如何在cocos2dx中访问object函数以及Apple Api

    转自:http://www.himigame.com/iphone-cocos2dx/743.html Cocos2dx系列博文的上一篇详细介绍了如何在Xcode中利用jni调用Android的Jav ...

  6. vue中eslintrc.js配置最详细介绍

    本文是对vue项目中自带文件eslintrc.js的内容解析, 介绍了各个eslint配置项的作用,以及为什么这样设置. 比较详细,看完能对eslint有较为全面的了解,基本解除对该文件的疑惑. /* ...

  7. Nginx配置中运行与启动的详细介绍【转】

    原文:http://developer.51cto.com/art/201003/190944.htm 我们在进行Nginx配置的时候会出现很多不明白的地方,其实有些时候只要换一个思维的方式就能找多你 ...

  8. (转载)QT中PRO文件写法的详细介绍,很有用,很重要!

    版权声明:本文为博主原创文章,未经博主允许不得转载. 在QT中,有一个工具qmake可以生成一个makefile文件,它是由.pro文件生成而来的,.pro文件的写法如下: 1. 注释从“#”开始,到 ...

  9. MongoDB中4种日志的详细介绍

    前言 任何一种数据库都有各种各样的日志,MongoDB也不例外.MongoDB中有4种日志,分别是系统日志.Journal日志.oplog主从日志.慢查询日志等.这些日志记录着MongoDB数据库不同 ...

  10. javaScript系列:js中获取时间new Date()详细介绍

    var myDate = new Date();myDate.getYear(); //获取当前年份(2位)myDate.getFullYear(); //获取完整的年份(4位,1970-????)m ...

随机推荐

  1. 【Unity】Timeline探索记(4)第二个例子——动作特写/子弹时间

    写在前面 这次例子参考这篇实现博文(附带项目下载),博文前面介绍非常具体,可惜后面特写轨实现代码不是按照我想要的标准四大件(data.mixer.clip.track)来组织的,所以这里我略过介绍,只 ...

  2. easy-rsa 不同版本克隆

    1.克隆easy-rsa 并切换到2.0版本的分支 yum install -y git git clone https://github.com/OpenVPN/easy-rsa.git cd ea ...

  3. Linux下将普通用户文件移动到root用户下

    步骤: 将普通用户的文件拷贝到tmp目录下 cp /Desktop/1.txt /tmp 从普通用户切到root用户 su - root用户从tmp中取文件到指定目录/var/test cp /tmp ...

  4. Otto Group Product Classification

    遇到的坑: 做多分类,用CrossEntropyLoss时,训练时候的正确标签的范围应该是[0,n-1],而不是[1,n],不然会报 IndexError: Target is out of boun ...

  5. [C#] Func及Action的快速Demo

    参考代码1: using System;using System.Collections.Generic;using System.Linq;namespace FuncActionDemo{    ...

  6. 我是如何被我的c++大作业榨干的

    手把手教你用git上传项目到GitHub(图文并茂,这一篇就够了),相信你一定能成功!! - 知乎 (zhihu.com) C++ 类对象数组_Slience_Perseverance的博客-CSDN ...

  7. setTimeout的那些事

      一.我们可以设置一个定时器用以指定某个程序在指定时间后才执行 1 setTimeout(function(){ 2 console.log("do something"); 3 ...

  8. 一文快速回顾 Java 操作数据库的方式-JDBC

    前言 数据库的重要性不言而喻,不管是什么系统,什么应用软件,也不管它们是 Windows 上的应用程序,还是 Web 应用程序,存储(持久化)和查询(检索)数据都是核心的功能. 大家学习数据库时,比如 ...

  9. Android笔记--通过MediaStore查询图片

    相关描述 已经完成发送彩信功能之后,就来继续向前走一步,来到MediaStore查询图片界面啦! 具体步骤实现 1.简简单单地一个界面 <?xml version="1.0" ...

  10. 使用MyBatis时需要注意到的事情------执行添加、修改和删除操作时,一定要记得提交事务

    今天在重写添加操作代码时,发现自己写的代码没有任何报错,使用断点进行查询,发现一切正常,但是注册使用的数据就是无法添加到数据库里面 然后就去之前看过的视频里面去找错误,就发现这样一个小细节: 在视频里 ...