Go中的有限状态机FSM的详细介绍 _
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 mainimport ("context""fmt""github.com/looplab/fsm"
)type Door struct {Name stringFSM *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,状态机的State由Src->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 mainimport ("context""fmt""github.com/looplab/fsm"
)type Door struct {Name stringFSM *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
相关文章:
Go中的有限状态机FSM的详细介绍 _
1、FSM简介 1.1 有限状态机的定义 有限状态机(Finite State Machine,FSM)是一种数学模型,用于描述系统在不同状态下的行为和转移条件。 状态机有三个组成部分:状态(State)、事件(…...
Python入门教程 | Python3 基本数据类型
赋值 Python 中的变量不需要声明。每个变量在使用前都必须赋值,变量赋值以后该变量才会被创建。 在 Python 中,变量就是变量,它没有类型,我们所说的"类型"是变量所指的内存中对象的类型。 等号(ÿ…...
STM32移植u8g2玩转oled 用软件iic实现驱动oled
移植u8g2到stm int fputc(int ch,FILE *f) {ITM_SendChar(ch);return (ch); }void delay_us(uint32_t time) {uint32_t i8*time;while(i--); }uint8_t STM32_gpio_and_delay(u8x8_t *u8x8, uint8_t msg, uint8_t arg_int, void *arg_ptr) {//printf("%s:msg %d,arg_int …...
C++ 学习系列 -- string 实现
string是C标准库的重要部分,主要用于字符串处理。这里我们自己实现一个简单版本的 string. 一 思路 string 类中应该包含如下: 1. 类成员变量:char* m_data,利用 char* 指针存放字符串 2. 成员函数: 2.1 size(…...
C语言小练习(三)
🌞 “也许你感觉自己与周遭格格不入,但正是那些你一人度过的时光,让你变得越来越有意思,等有天别人终于注意到你的时候,他们就会发现一个比他们想象中更酷的人。”-《生活大爆炸》 Day03 📝 一.选择题&…...
2023 js逆向爬虫 有道翻译 代码
前置条件:nodejs环境、安装 crypto 和 python3环境 js.js文件: const crypto require("crypto")function decode(resp_data) {g_o ydsecret://query/key/B*RGygVywfNBwpmBaZg*WT7SIOUP2T0C9WHMZN39j^DAdaZhAnxvGcCY6VYFwnHlg_n ydsecre…...
【物联网无线通信技术】NFC从理论到实践(FM17XX)
NFC,全称是Near Field Communication,即“近场通信”,也叫“近距离无线通信”。NFC诞生于2004年,是基于RFID非接触式射频识别技术演变而来,由当时的龙头企业NXP(原飞利浦半导体)、诺基亚以及索尼联合发起。NFC采用13.5…...
Python爬虫猿人学逆向系列——第六题
题目:采集全部5页的彩票数据,计算全部中奖的总金额(包含一、二、三等奖) 地址:https://match.yuanrenxue.cn/match/6 本题比较简单,只是容易踩坑。话不多说请看分析。 两个参数,一个m一个f&…...
idea使用tomcat
1. 建立javaweb项目 2. /WEB-INF/web.xml项目配置文件 如果javaweb项目 先建立项目,然后在项目上添加框架支持,选择javaee 3. 项目结构 4.执行测试:...
搭建Tomcat HTTP服务:在Windows上实现外网远程访问的详细配置与设置教程
文章目录 前言1.本地Tomcat网页搭建1.1 Tomcat安装1.2 配置环境变量1.3 环境配置1.4 Tomcat运行测试1.5 Cpolar安装和注册 2.本地网页发布2.1.Cpolar云端设置2.2 Cpolar本地设置 3.公网访问测试4.结语 前言 Tomcat作为一个轻量级的服务器,不仅名字很有趣࿰…...
Java学习笔记——继承(包括this,super的使用总结)
继承: 使用情景:当类与类之间,存在相同(共性)的内容,并满足子类是父类的一种,就可以考虑使用继承,来优化代码 Java中提供一个关键字extends,用这个关键字,我…...
Android 获取应用sha1和sha256
在 Android 应用开发中,SHA-1(Secure Hash Algorithm 1)值是一种哈希算法,常用于生成应用的数字签名。这个数字签名用于验证应用的身份,并确保应用在发布到设备上时没有被篡改。 以下是生成 Android 应用的 SHA-1 值的…...
c# 方法参数修饰符(out、ref、in)的区别
在C#中,ref、out和in是三种方法参数修饰符,它们在传递参数的方式和作用上有所不同。 ref修饰符: 传递方式:使用ref修饰符的参数可以是输入输出参数,即在方法调用前后都可以对其进行修改。 作用:通过ref修…...
shell 编写一个带有进度条的程序安装脚本
需求 使用 shell 写一个 软件安装脚本,带有进度条 示例 #!/bin/bash# 模拟软件安装的步骤列表 steps("解压文件" "安装依赖" "配置设置" "复制文件" "")# 计算总步骤数 total_steps${#steps[]}# 安装进度的初…...
服务器数据恢复-AIX PV完整镜像方法以及误删LV的数据恢复方案
AIX中的PV相当于物理磁盘(针对于存储来说,PV相当于存储映射过来的卷;针对操作系统来说,PV相当于物理硬盘),若干个PV组成一个VG,AIX可以将容量不同的存储空间组合起来统一分配。AIX把同一个VG的所…...
首席执行官Adam Selipsky解读“亚马逊云科技的技术产品差异化”
迄今为止,亚马逊云科技已经参与了21世纪几乎所有的大型计算变革,亚马逊云科技是一个很传奇的故事,它始于大约20年前的一项实验,当时亚马逊试图出售其过剩的服务器。人们确实对此表示怀疑。为什么在线书店试图销售云服务࿱…...
C++ Day3
目录 一、类 【1】类 【2】应用实例 练习: 【3】封装 二、this指针 【1】this指针的格式 【2】必须使用this指针的场合 三、类中的特殊成员函数 【1】构造函数 i)功能 ii)格式 iii)构造函数的调用时机 iv)…...
OpenEuler 安装mysql
下载安装包 建议直接使用在openEuler官方编译移植过的mysql-5.7.21系列软件包 参考:操作系统迁移实战之在openEuler上部署MySQL数据库 | 数据库迁移方案 | openEuler社区官网 MySQL 5.7.21 移植指南(openEuler 20.03 LTS SP1) | 数据库移植…...
[Docker] Windows 下基于WSL2 安装
Docker 必须部署在 Linux 内核的系统上。如果其他系统想部署 Docker 就必须安装一个虚拟 Linux 环境。 1. 开启虚拟化 进入系统BIOS(AMD 为 SVM;Intel 为 Intel-vt)改为启用(enable) 2. 开启WSL 系统设置->应用->程序和功能->…...
(未完成)【Spring专题】SringAOP底层原理解析——阶段三(AOP)
目录 前言前置知识代理范式SpringAOP的理解Spring动态代理的实现 课程内容一、动态代理的实现1.1 Cglib动态代理1.2 JDK动态代理1.3 ProxyFactory:Spring对两种代理的封装 二、AOP基础知识AOP基础概念回顾通知Advice的分类Advisor的理解 三、创建代理对象的方式3.1 …...
(LeetCode 每日一题) 3442. 奇偶频次间的最大差值 I (哈希、字符串)
题目:3442. 奇偶频次间的最大差值 I 思路 :哈希,时间复杂度0(n)。 用哈希表来记录每个字符串中字符的分布情况,哈希表这里用数组即可实现。 C版本: class Solution { public:int maxDifference(string s) {int a[26]…...
React第五十七节 Router中RouterProvider使用详解及注意事项
前言 在 React Router v6.4 中,RouterProvider 是一个核心组件,用于提供基于数据路由(data routers)的新型路由方案。 它替代了传统的 <BrowserRouter>,支持更强大的数据加载和操作功能(如 loader 和…...
uni-app学习笔记二十二---使用vite.config.js全局导入常用依赖
在前面的练习中,每个页面需要使用ref,onShow等生命周期钩子函数时都需要像下面这样导入 import {onMounted, ref} from "vue" 如果不想每个页面都导入,需要使用node.js命令npm安装unplugin-auto-import npm install unplugin-au…...
蓝牙 BLE 扫描面试题大全(2):进阶面试题与实战演练
前文覆盖了 BLE 扫描的基础概念与经典问题蓝牙 BLE 扫描面试题大全(1):从基础到实战的深度解析-CSDN博客,但实际面试中,企业更关注候选人对复杂场景的应对能力(如多设备并发扫描、低功耗与高发现率的平衡)和前沿技术的…...
Neo4j 集群管理:原理、技术与最佳实践深度解析
Neo4j 的集群技术是其企业级高可用性、可扩展性和容错能力的核心。通过深入分析官方文档,本文将系统阐述其集群管理的核心原理、关键技术、实用技巧和行业最佳实践。 Neo4j 的 Causal Clustering 架构提供了一个强大而灵活的基石,用于构建高可用、可扩展且一致的图数据库服务…...
Matlab | matlab常用命令总结
常用命令 一、 基础操作与环境二、 矩阵与数组操作(核心)三、 绘图与可视化四、 编程与控制流五、 符号计算 (Symbolic Math Toolbox)六、 文件与数据 I/O七、 常用函数类别重要提示这是一份 MATLAB 常用命令和功能的总结,涵盖了基础操作、矩阵运算、绘图、编程和文件处理等…...
数据库分批入库
今天在工作中,遇到一个问题,就是分批查询的时候,由于批次过大导致出现了一些问题,一下是问题描述和解决方案: 示例: // 假设已有数据列表 dataList 和 PreparedStatement pstmt int batchSize 1000; // …...
高防服务器能够抵御哪些网络攻击呢?
高防服务器作为一种有着高度防御能力的服务器,可以帮助网站应对分布式拒绝服务攻击,有效识别和清理一些恶意的网络流量,为用户提供安全且稳定的网络环境,那么,高防服务器一般都可以抵御哪些网络攻击呢?下面…...
.Net Framework 4/C# 关键字(非常用,持续更新...)
一、is 关键字 is 关键字用于检查对象是否于给定类型兼容,如果兼容将返回 true,如果不兼容则返回 false,在进行类型转换前,可以先使用 is 关键字判断对象是否与指定类型兼容,如果兼容才进行转换,这样的转换是安全的。 例如有:首先创建一个字符串对象,然后将字符串对象隐…...
安卓基础(aar)
重新设置java21的环境,临时设置 $env:JAVA_HOME "D:\Android Studio\jbr" 查看当前环境变量 JAVA_HOME 的值 echo $env:JAVA_HOME 构建ARR文件 ./gradlew :private-lib:assembleRelease 目录是这样的: MyApp/ ├── app/ …...
