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 …...
基于算法竞赛的c++编程(28)结构体的进阶应用
结构体的嵌套与复杂数据组织 在C中,结构体可以嵌套使用,形成更复杂的数据结构。例如,可以通过嵌套结构体描述多层级数据关系: struct Address {string city;string street;int zipCode; };struct Employee {string name;int id;…...
超短脉冲激光自聚焦效应
前言与目录 强激光引起自聚焦效应机理 超短脉冲激光在脆性材料内部加工时引起的自聚焦效应,这是一种非线性光学现象,主要涉及光学克尔效应和材料的非线性光学特性。 自聚焦效应可以产生局部的强光场,对材料产生非线性响应,可能…...
vue3 定时器-定义全局方法 vue+ts
1.创建ts文件 路径:src/utils/timer.ts 完整代码: import { onUnmounted } from vuetype TimerCallback (...args: any[]) > voidexport function useGlobalTimer() {const timers: Map<number, NodeJS.Timeout> new Map()// 创建定时器con…...
Spring Boot+Neo4j知识图谱实战:3步搭建智能关系网络!
一、引言 在数据驱动的背景下,知识图谱凭借其高效的信息组织能力,正逐步成为各行业应用的关键技术。本文聚焦 Spring Boot与Neo4j图数据库的技术结合,探讨知识图谱开发的实现细节,帮助读者掌握该技术栈在实际项目中的落地方法。 …...
关于 WASM:1. WASM 基础原理
一、WASM 简介 1.1 WebAssembly 是什么? WebAssembly(WASM) 是一种能在现代浏览器中高效运行的二进制指令格式,它不是传统的编程语言,而是一种 低级字节码格式,可由高级语言(如 C、C、Rust&am…...
QT: `long long` 类型转换为 `QString` 2025.6.5
在 Qt 中,将 long long 类型转换为 QString 可以通过以下两种常用方法实现: 方法 1:使用 QString::number() 直接调用 QString 的静态方法 number(),将数值转换为字符串: long long value 1234567890123456789LL; …...
C++使用 new 来创建动态数组
问题: 不能使用变量定义数组大小 原因: 这是因为数组在内存中是连续存储的,编译器需要在编译阶段就确定数组的大小,以便正确地分配内存空间。如果允许使用变量来定义数组的大小,那么编译器就无法在编译时确定数组的大…...
#Uniapp篇:chrome调试unapp适配
chrome调试设备----使用Android模拟机开发调试移动端页面 Chrome://inspect/#devices MuMu模拟器Edge浏览器:Android原生APP嵌入的H5页面元素定位 chrome://inspect/#devices uniapp单位适配 根路径下 postcss.config.js 需要装这些插件 “postcss”: “^8.5.…...
Chrome 浏览器前端与客户端双向通信实战
Chrome 前端(即页面 JS / Web UI)与客户端(C 后端)的交互机制,是 Chromium 架构中非常核心的一环。下面我将按常见场景,从通道、流程、技术栈几个角度做一套完整的分析,特别适合你这种在分析和改…...
Vue3中的computer和watch
computed的写法 在页面中 <div>{{ calcNumber }}</div>script中 写法1 常用 import { computed, ref } from vue; let price ref(100);const priceAdd () > { //函数方法 price 1price.value ; }//计算属性 let calcNumber computed(() > {return ${p…...
