惯性聚合 高效追踪和阅读你感兴趣的博客、新闻、科技资讯
阅读原文 在惯性聚合中打开

推荐订阅源

F
Fortinet All Blogs
让小产品的独立变现更简单 - ezindie.com
让小产品的独立变现更简单 - ezindie.com
有赞技术团队
有赞技术团队
www.infosecurity-magazine.com
www.infosecurity-magazine.com
大猫的无限游戏
大猫的无限游戏
爱范儿
爱范儿
Threat Intelligence Blog | Flashpoint
Threat Intelligence Blog | Flashpoint
T
Threatpost
V
Visual Studio Blog
Apple Machine Learning Research
Apple Machine Learning Research
博客园 - Franky
人人都是产品经理
人人都是产品经理
cs.AI updates on arXiv.org
cs.AI updates on arXiv.org
The Cloudflare Blog
N
News and Events Feed by Topic
L
Lohrmann on Cybersecurity
cs.CL updates on arXiv.org
cs.CL updates on arXiv.org
酷 壳 – CoolShell
酷 壳 – CoolShell
V
V2EX
AWS News Blog
AWS News Blog
S
SegmentFault 最新的问题
T
Tailwind CSS Blog
Hugging Face - Blog
Hugging Face - Blog
钛媒体:引领未来商业与生活新知
钛媒体:引领未来商业与生活新知
Spread Privacy
Spread Privacy
J
Java Code Geeks
博客园 - 聂微东
T
Tor Project blog
宝玉的分享
宝玉的分享
博客园 - 叶小钗
Webroot Blog
Webroot Blog
博客园 - 【当耐特】
freeCodeCamp Programming Tutorials: Python, JavaScript, Git & More
H
Heimdal Security Blog
Y
Y Combinator Blog
T
The Blog of Author Tim Ferriss
MongoDB | Blog
MongoDB | Blog
I
InfoQ
Security Latest
Security Latest
Martin Fowler
Martin Fowler
Hacker News: Ask HN
Hacker News: Ask HN
P
Privacy International News Feed
C
CERT Recently Published Vulnerability Notes
Latest news
Latest news
雷峰网
雷峰网
D
Darknet – Hacking Tools, Hacker News & Cyber Security
C
Cisco Blogs
H
Help Net Security
L
LINUX DO - 最新话题
L
LINUX DO - 热门话题

又见苍岚

COLMAP PatchMatch Stereo 算法详解 Git 在国内网络环境下无法 Push 的排查与修复 —— 配置 Clash 代理 分段五次多项式插值原理详解 路径插值方法深度对比研究 Claude Code 使用指南 OpenClaw 记忆管理与技能创建指南 CBS(Conflict-Based Search)算法详解 A* 算法及其变种详解 OpenClaw 配置多 Agents Windows Powershell 无法加载文件,因为在此系统上禁止运行脚本问题的解决方案 MaxClaw 安装流程 大模型 AI 名词介绍 AList 网盘聚合工具简介 Protobuf 简介与测试 Claude Code 简介以及 GLM 4.7 模型接入 Github 歌词下载工具 163MusicLyrics Python __getattr__ 懒加载 Python TypedDict 机器人仿真平台 Gazebo 安装记录 机器人仿真平台 Gazebo 简介 多机器人路径规划问题(Multi-Agent Path Finding, MAPF)简介 Python exifread 读取修改过的 jpeg 信息错误问题修复 3D 坐标系变换的理解 3D 旋转矩阵基本概念 MongoDB Compass 介绍 Python 环境管理工具 uv Flutter 开发指南 Snipaste 安装下载与黑屏问题解决方案 全局路径规划算法记录 2025 Python 版本性能测试 Flutter Hello World Flutter 安装环境配置 Ubuntu VMware 硬盘扩容后 SMBus Host controller not enabled 报错问题解决 Python NetworkX 教程 Docker GPU 报错 - Failed to initialize NVML Unknown Error 解决方案 Python matplotlib 图表绘制 cuda-toolkit 安装替代 Cuda 与 Cudnn Jinja2 Python 利用 docxtpl 和 Jinja2 生成基于模板的 Word 文档 Docker 实现 CPU 核心隔离 LoFTR 基于 Transformer 的特征提取匹配算法 OmniGlue 特征匹配 SuperGlue 使用图神经网络学习特征匹配 Ubuntu 下将 xlsx 文件按照 sheet 转换为 图片 Python 使用 SQLAlchemy Python FastAPI 教程 openwrt 软路由配置安装 Nav2 地图文件(PGM/YAML)规范标准 3D OBJ 模型转换为 glb 瓦片格式 Python 源码 Redis 数据库介绍 Ubuntu 22.04 内核自动升级导致 MongoDB 7.0.12 错误记录 ubuntu 20.04 安装 ROS Noetic ubuntu 18.04 安装 ROS Melodic VMware Workstation Pro 个人免费版下载、安装、使用指南 Hybrid A-star 路径规划 Reeds-Shepp 曲线 Dubins 曲线 Linux kvm 虚拟机网络不通的问题解决方法 Ubuntu 自动内存清理 BiliBili 缓存视频转 mp4 Python 求解线性规划 3D Gaussian Splatting 官方源码实践记录 ImageMagick 教程 Ubuntu 22.04 安装 Colmap 对数几率 odds Ubuntu nmcli 网络管理工具使用指南 SuperPoint 自监督深度学习特征点提取 SyncTV Music Tag Web 在线音乐信息整理工具 ncm 格式转 mp3 MusicBrainz 音乐元数据百科数据库 Ubuntu 网络流量监控工具 私人云音乐平台 Navidrome 入门 手眼标定 四元数(Quaternions) OHTTPS 实现免费自动 https 证书申请、更新、部署 ubuntu 22.04 安装 CloudCompare 单机 KVM 虚拟机冷迁移 Ubuntu 22.04 使用 mdadm 实现软 raid 小鱼 一键安装 ROS-humble Fluid -46- 基于 Simpletex API 构建公式识别页面 公式识别 API 简介 -- Simpletex 使用 Python web 部署库 waitress 3D Gaussian Splatting for Real-Time Radiance Field Rendering Ubuntu Swap 简介与空间扩展 Ubuntu 24.04 安装 forticlient Clash Verge 使用 MongoDB 7.0.17 集群 Docker 构建源码 Error code - 2013. Lost connection to MySQL server during query 问题解决 Python 日志记录库 loguru 使用指北 Python 实现 Web 日志查看服务 MySQL LOAD DATA LOCAL INFILE 极速数据加载 Image size exceeds limit of 89478485 pixels 解决方案 Docker 使用 NVIDIA GPU 驱动错误解决 阿里云 docker 镜像仓库 Ubuntu中没有wired connected的解决方案 MinIO 简介 subconverter 代理订阅格式转换 修复 node –openssl-legacy-provider is not allowed in NODE_OPTIONS 错误 Ubuntu 使用 Clash For Linux 客户端
事件驱动的状态机框架:从理论到工程实践
Yiwei Zhang · 2026-04-21 · via 又见苍岚

深入理解事件驱动的有限状态机(FSM)与状态图(Statecharts)的设计原理,涵盖核心概念、架构模式、主流框架对比以及工程实践中的最佳实践。

1 引言

在软件工程中,状态机(State Machine)是最古老也最经久不衰的建模工具之一。从数字电路设计到网络协议解析,从嵌入式系统到现代前端应用,状态机无处不在。而将状态机与 事件驱动(Event-Driven)范式结合,便诞生了一种强大的架构模式——事件驱动的状态机框架

这种模式的核心思想很简单:

系统在任意时刻处于有限的若干状态之一,当外部事件到达时,根据当前状态和事件类型,决定是否切换到新状态,并执行相应的动作。

这看似朴素的模型,却能优雅地解决现实世界中大量复杂的状态管理问题。本文将从理论基础出发,逐步深入到工程实践。

2 理论基础

2.1 有限状态机(FSM)

有限状态机(Finite State Machine,FSM)是一种数学计算模型,由以下五元组定义:

$$ \text{FSM} = (S, \Sigma, \delta, s_0, F) $$

其中:

符号 含义
$S$ 有限状态集合
$\Sigma$ 有限输入事件(字母表)
$\delta: S \times \Sigma \rightarrow S$ 状态转换函数
$s_0 \in S$ 初始状态
$F \subseteq S$ 终止状态集合

FSM 的关键特性是 确定性(Deterministic):给定当前状态和输入事件,下一个状态是唯一确定的。这种确定性使得 FSM 的行为完全可预测,极大地简化了测试和验证。

2.2 Mealy 机与 Moore 机

根据输出与状态、输入的关系,FSM 可以分为两类:

  • Moore 机:输出仅取决于当前状态。每个状态关联一组固定的输出动作。
  • Mealy 机:输出取决于当前状态和输入事件。动作在状态转换时触发。

在软件实践中,Mealy 机更为常见——我们通常在转换(transition)上定义动作,而不是在状态上。但现代框架往往同时支持两种模式。

2.3 从 FSM 到 Statecharts

传统 FSM 有一个致命弱点:状态爆炸。当系统复杂度增长时,状态数量呈指数级膨胀。David Harel 在 1987 年提出了 Statecharts(状态图)来解决这个问题,引入了三个关键扩展:

  1. 层次化状态(Hierarchical States):状态可以嵌套,子状态继承父状态的转换规则
  2. 并行状态(Parallel States):多个正交的区域可以同时处于活动状态
  3. 通信机制:状态之间可以通过事件进行通信

Statecharts 是 FSM 的严格超集——每个 FSM 都是 Statecharts,但 Statecharts 可以用更少的图元表达更复杂的逻辑。

3 事件驱动架构

3.1 什么是事件驱动

事件驱动编程范式的核心是 控制反转(Inversion of Control):

1
2
3
4
5
6
7
8
9
10
11
传统轮询模式:
while (true) {
if (condition_A()) handle_A();
if (condition_B()) handle_B();
// 持续轮询...
}

事件驱动模式:
eventQueue.on('event_A', handle_A);
eventQueue.on('event_B', handle_B);
// 被动等待事件到来

在传统模式中,程序主动检查条件(轮询);在事件驱动模式中,程序被动响应外部事件。这种反转带来了几个重要优势:

  • 低耦合:事件生产者和消费者互不感知
  • 高响应性:无需浪费时间在无效轮询上
  • 天然异步:事件可以被排队、延迟、优先级排序

3.2 事件循环与 Run-to-Completion

事件驱动系统的核心是 事件循环(Event Loop),其基本工作方式:

1
2
3
4
5
while (running) {
event = getNextEvent(); // 从队列取事件
dispatch(event); // 分发给处理器
// 处理器必须快速完成(RTC)
}

Run-to-Completion(RTC) 是关键约束:每个事件处理器必须在处理下一个事件之前完成。这意味着:

  • 事件处理期间不会被其他事件中断(单线程视角)
  • 状态转换是原子的
  • 不存在竞态条件(在单个 Active Object 内部)

3.3 Active Object 模式

将事件驱动与 FSM 结合,最经典的架构模式是 Active Object(又称 Actor 模式)。其核心原则来自并发编程的最佳实践:

  1. 数据隔离:每个 Active Object 拥有自己的私有状态,不与外部共享
  2. 异步通信:Active Object 之间通过事件(消息)异步通信,不阻塞等待
  3. 事件循环:每个 Active Object 花费其生命周期响应传入事件
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
┌─────────────────────────────┐
│ Active Object
│ ┌───────────────────────┐ │
│ │ Event Queue │ │
│ │ [e1] [e2] [e3] ... │ │
│ └──────────┬────────────┘ │
│ │ │
│ ▼ │
│ ┌───────────────────────┐ │
│ │ State Machine │ │
│ │ │ │
│ │ [State A] ──►[State B]│ │
│ │ │ │ │ │
│ │ ▼ ▼ │ │
│ │ Actions Actions │
│ └───────────────────────┘ │
│ │ │
│ ▼ │
│ ┌───────────────────────┐ │
│ │ Private Data │ │
│ └───────────────────────┘ │
└─────────────────────────────┘

│ async events

Other Active Objects

这种模式在 Erlang/OTP、Akka(JVM)、QP(嵌入式)等框架中被广泛采用。它提供了真正的封装性——比传统 OOP 的封装更加严格,因为并发访问被事件队列序列化,无需互斥锁。

4 核心概念详解

4.1 状态(State)

状态是系统在某一时刻的完整快照。在 FSM 中,系统在任意时刻恰好处于一个状态(或 Statecharts 中的一组并行状态)。

状态可以分为以下几类:

类型 描述 示例
原子状态 不可再分的最小状态单元 “开”、“关”
组合状态 包含子状态的层次化状态 “运行中”(含"加速"、“匀速”、“减速”)
并行状态 多个区域同时活动 “播放中” 同时有"音频状态"和"视频状态"
终止状态 流程结束的标记 “完成”、“错误”
历史状态 记住上次离开时的子状态 暂停后恢复到之前的进度

4.2 事件(Event)

事件是触发状态变化的信号。它通常是一个不可变的数据结构:

1
2
3
4
5
interface Event {
type: string; // 事件类型
payload?: any; // 携带的数据
timestamp?: number; // 时间戳
}

事件的来源多种多样:

  • 用户交互:点击、输入、手势
  • 系统信号:定时器、网络回调、硬件中断
  • 内部生成:状态机自身的 raise / send 动作
  • 外部系统:消息队列、WebSocket、IPC

4.3 转换(Transition)

转换定义了状态变化的规则。一个完整的转换包含:

1
[源状态] + [触发事件] + [守卫条件][目标状态] + [动作]
  • 外部转换:退出源状态,进入目标状态(触发 exit/entry 动作)
  • 内部转换:不改变当前状态,仅执行动作(不触发 exit/entry)
  • 自转换:源状态和目标状态相同,但会触发 exit/entry
  • 延迟转换:在状态中停留指定时间后自动触发

4.4 动作(Action)

动作是状态转换时的副作用,也称为效果(Effect)。可以在以下时机触发:

时机 描述 常见用途
entry 进入状态时 初始化、启动定时器
exit 退出状态时 清理、停止定时器
transition 转换发生时 日志、网络请求
do 状态中持续执行 动画、轮询

4.5 守卫条件(Guard)

守卫条件是一个布尔函数,决定转换是否被允许执行:

1
2
3
4
5
// 只有余额充足时才允许转换到"购买成功"
{
target: 'purchaseSuccess',
guard: ({ context }) => context.balance >= context.price
}

守卫条件使得同一个事件在不同条件下可以导向不同的目标状态,增强了表达的灵活性。

4.6 上下文(Context)

上下文(也称扩展状态 Extended State)是与状态机关联的可变数据:

1
2
3
4
5
6
7
8
const machine = createMachine({
context: {
retries: 0,
maxRetries: 3,
lastError: null,
},
// ...
});

上下文弥补了纯 FSM 的一个局限——有限状态无法表达无限的数值域。通过引入上下文,我们可以用有限的状态来表达无限的数据变化。

5 设计模式与实践

5.1 状态设计原则

设计状态时,遵循以下原则可以避免常见的陷阱:

原则一:状态应代表"是什么",而非"做了什么"

1
2
3
4
5
// ❌ 错误:状态中混入了行为描述
states: ['loading', 'showingError', 'submittingForm']

// ✅ 正确:状态描述系统的客观情况
states: ['idle', 'loading', 'error', 'success']

原则二:用状态图穷举所有可能性

在设计阶段,就应当用状态图列出所有状态和转换。这一过程本身就能发现许多潜在的 bug 和遗漏的场景。

原则三:避免"不可能的状态"

1
2
3
4
5
// ❌ 错误:isLoading 和 error 可能同时为 true
{ isLoading: true, error: '网络错误' }

// ✅ 正确:互斥的状态
state: 'loading' | 'error' | 'success'

5.2 常见设计模式

模式一:单一状态变量

将应用状态建模为一个枚举,而非一组布尔标志:

1
2
3
4
5
// ❌ 布尔标志组合
{ isIdle, isLoading, isSuccess, isError }

// ✅ 单一状态枚举
type State = 'idle' | 'loading' | 'success' | 'error';

模式二:状态 + 上下文

分离"有限状态"和"无限数据":

1
2
3
4
5
6
interface MachineContext {
data: null | Response;
error: null | Error;
retryCount: number;
}
type State = 'idle' | 'loading' | 'success' | 'error';

模式三:层次化建模

复杂流程用嵌套状态管理:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
const appMachine = createMachine({
initial: 'unauthenticated',
states: {
unauthenticated: {
initial: 'login',
states: {
login: {},
register: {},
forgotPassword: {},
}
},
authenticated: {
initial: 'dashboard',
states: {
dashboard: {},
settings: {},
profile: {},
}
}
}
});

5.3 测试策略

事件驱动状态机的确定性使得测试非常直观:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
// 测试状态转换
const actor = createActor(loginMachine);
actor.start();

actor.send({ type: 'submit', username: 'test', password: '123' });
expect(actor.getSnapshot().value).toBe('loading');

// 测试守卫条件
actor.send({ type: 'success', data: mockData });
expect(actor.getSnapshot().value).toBe('authenticated');

// 测试不应发生的转换
actor.send({ type: 'submit' }); // 已认证状态不应接受 submit
expect(actor.getSnapshot().value).toBe('authenticated'); // 状态不变

6 主流框架对比

6.1 框架概览

框架 语言 特点 适用场景
XState TypeScript/JS Statecharts 完整实现、可视化、Actor 模型 前端应用、全栈
transitions Python 轻量、OO 友好、支持层次化 数据处理、后端
Spring StateMachine Java 企业级、与 Spring 生态集成 微服务、工作流
QP/QP-nano C/C++ 嵌入式优化、Active Object、零依赖 实时系统、嵌入式
Akka FSM Scala/Java Actor 模型、分布式 分布式后端
Robot Framework 通用 测试自动化中的状态管理 自动化测试
SMC (State Machine Compiler) 多语言 从状态图定义生成代码 跨平台项目

6.2 XState(TypeScript)

XState 是目前最成熟的 Statecharts 实现之一,由 David Khourshid(@davidkpiano)创建,当前版本为 v5。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
import { createMachine, assign, createActor } from 'xstate';

const fetchMachine = createMachine({
id: 'fetch',
initial: 'idle',
context: {
data: null,
error: null,
retries: 0,
},
states: {
idle: {
on: {
FETCH: { target: 'loading' }
}
},
loading: {
entry: assign({ retries: ({ context }) => context.retries + 1 }),
invoke: {
src: 'fetchData',
onSuccess: { target: 'success', actions: assign({ data: ({ event }) => event.data }) },
onError: [
{ guard: ({ context }) => context.retries < 3, target: 'loading' },
{ target: 'failure', actions: assign({ error: ({ event }) => event.error }) }
]
}
},
success: {
on: {
FETCH: { target: 'loading' }
}
},
failure: {
on: {
RETRY: { target: 'loading' }
}
}
}
});

XState 的核心优势:

  • 可视化:Stately Studio 提供交互式编辑器
  • 类型安全:完整的 TypeScript 类型推导
  • Actor 模型:支持 spawn、消息传递、系统级管理
  • 测试友好:内置模型测试(Model-based Testing)

6.3 transitions(Python)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
from transitions import Machine

class Task:
states = ['todo', 'in_progress', 'review', 'done', 'failed']

def __init__(self):
self.machine = Machine(
model=self,
states=Task.states,
initial='todo'
)
self.machine.add_transition('start', 'todo', 'in_progress')
self.machine.add_transition('submit', 'in_progress', 'review')
self.machine.add_transition('approve', 'review', 'done')
self.machine.add_transition('reject', 'review', 'in_progress')
self.machine.add_transition('fail', ['in_progress', 'review'], 'failed')
self.machine.add_transition('reset', 'failed', 'todo')

task = Task()
task.start() # todo -> in_progress
task.submit() # in_progress -> review
task.reject() # review -> in_progress

transitions 支持 HSM(层次化状态机)、Graphviz 图形生成、异步扩展等。

6.4 QP(C/C++)

QP(Quantum Platform)是嵌入式领域最知名的事件驱动框架,由 Miro Samek 创建。它直接实现了 Active Object 模式:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
// 定义事件
typedef struct {
QEvt super; // 基类事件
uint16_t key; // 键码
} KeyboardEvt;

// 定义 Active Object
typedef struct {
QActive super; // 基类
uint8_t volume;
} PlayerAO;

// 状态处理函数
QState Player_initial(PlayerAO *me, void const *par) {
return Q_TRAN(&Player_idle);
}

QState Player_idle(PlayerAO *me, QEvt const *e) {
switch (e->sig) {
case PLAY_SIG:
return Q_TRAN(&Player_playing);
case OFF_SIG:
return Q_TRAN(&Player_off);
}
return Q_SUPER(&QHsm_top);
}

QState Player_playing(PlayerAO *me, QEvt const *e) {
switch (e->sig) {
case STOP_SIG:
return Q_TRAN(&Player_idle);
case KEY_PRESS_SIG: {
KeyboardEvt *ke = (KeyboardEvt *)e;
me->volume += ke->key;
return Q_HANDLED();
}
}
return Q_SUPER(&QHsm_top);
}

QP 的特色在于:

  • 零依赖:不依赖操作系统,可在裸机上运行
  • 确定性:适用于硬实时系统
  • 极小 footprint:QP-nano 可以在 8 位 MCU 上运行
  • 内置层次化状态机:支持 Statecharts 的核心特性

7 应用场景深度分析

7.1 前端应用状态管理

现代前端应用中,状态管理是最棘手的问题之一。传统的 “boolean flag” 模式容易产生 不可能状态

1
2
// 灾难:两个标志同时为 true
{ isLoading: true, isError: true, isSuccess: true }

使用状态机可以彻底消除这类问题,因为状态是互斥的。XState 与 React 的结合尤为流行:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
import { useMachine } from '@xstate/react';

function LoginComponent() {
const [state, send] = useMachine(loginMachine);

return (
<div>
{state.matches('idle') && <LoginForm onSubmit={() => send({ type: 'SUBMIT' })} />}
{state.matches('loading') && <Spinner />}
{state.matches('success') && <Navigate to="/dashboard" />}
{state.matches('error') && (
<ErrorMessage
error={state.context.error}
onRetry={() => send({ type: 'RETRY' })}
/>
)}
</div>
);
}

7.2 订单与工作流系统

电商订单是一个经典的状态机场景:

1
2
3
4
5
待支付 ──支付成功──► 待发货 ──发货──► 已发货 ──确认收货──► 已完成
│ │ │
│ 超时 │ 取消 │ 退货
▼ ▼ ▼
已取消 已取消 退货中 ──退款──► 已退款

在企业级场景中,Spring StateMachine 结合持久化存储,可以实现:

  • 状态持久化(数据库存储当前状态)
  • 分布式锁(防止并发状态冲突)
  • 事件溯源(记录所有状态变更历史)

7.3 游戏开发

游戏中的角色 AI 是状态机的经典应用:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
const enemyAI = createMachine({
initial: 'patrol',
context: { playerDistance: Infinity, health: 100 },
states: {
patrol: {
on: { PLAYER_DETECTED: 'chase' }
},
chase: {
on: {
IN_ATTACK_RANGE: 'attack',
PLAYER_LOST: 'patrol',
LOW_HEALTH: 'flee'
}
},
attack: {
on: {
TARGET_DEAD: 'patrol',
OUT_OF_RANGE: 'chase',
LOW_HEALTH: 'flee'
}
},
flee: {
on: {
SAFE: 'patrol',
DEAD: 'dead'
}
},
dead: { type: 'final' }
}
});

更复杂的 AI 会使用层次化状态:attack 状态内部可能包含 meleerangedspecial 等子状态。

7.4 嵌入式与 IoT

在嵌入式系统中,事件驱动的状态机几乎是唯一的架构选择:

  • 资源约束:无法承受传统 RTOS 的开销
  • 实时性要求:必须保证确定性响应时间
  • 事件来源多样:传感器、定时器、通信协议、用户输入

一个智能灯泡的简化状态机:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
// 状态:关灯、开灯白光、开灯暖光、配网中、OTA升级
// 事件:按键、MQTT消息、定时器、传感器
QState Bulb_onWhite(Bulb *me, QEvt const *e) {
switch (e->sig) {
case BUTTON_PRESS_SIG:
return Q_TRAN(&Bulb_warm);
case MQTT_CMD_SIG:
// 处理远程控制命令
handleMqttCmd(me, (MqttEvt *)e);
return Q_HANDLED();
case TIMEOUT_SIG:
return Q_TRAN(&Bulb_off);
}
return Q_SUPER(&Bulb_on);
}

7.5 网络协议解析

网络协议天然适合状态机建模。TCP 连接的状态机是教科书级别的案例:

1
2
3
4
5
CLOSED ──SYN──► SYN_SENT ──SYN+ACK──► ESTABLISHED

│ FIN

FIN_WAIT ──ACK──► TIME_WAIT ──超时──► CLOSED

HTTP 请求处理、WebSocket 连接管理、MQTT 消息解析等都可以用状态机优雅实现。

8 性能与权衡

8.1 性能特征

事件驱动状态机的性能特征:

方面 特征
状态查找 O(1) — 当前状态直接索引
事件分发 O(k) — k 为当前状态的可选转换数
内存占用 极小 — 仅存储当前状态 + 上下文
吞吐量 高 — 无锁、无阻塞
延迟 低 — 事件循环即时处理

8.2 何时使用状态机

适合使用状态机的场景:

  • 状态数量有限且明确
  • 状态转换规则复杂但有规律
  • 需要避免"不可能状态"
  • 需要可视化和文档化业务流程
  • 对可测试性要求高

不适合使用状态机的场景:

  • 状态数量极多或不确定
  • 状态转换完全随机
  • 简单的 CRUD 应用
  • 团队不熟悉状态机范式

8.3 常见反模式

反模式一:上帝状态机

将整个应用逻辑塞进一个状态机,导致状态爆炸。正确做法是将系统分解为多个小型、独立的状态机。

反模式二:滥用全局事件

将所有事件都设为全局可见,导致隐式耦合。正确做法是限定事件的可见范围,使用 Actor 模型进行显式通信。

反模式三:在守卫条件中执行副作用

守卫条件应该是纯函数,不应该修改状态或触发 I/O。

9 从零实现一个简易框架

为了加深理解,让我们用 TypeScript 实现一个最小化的事件驱动状态机:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
type State = string;
type Event = { type: string; [key: string]: any };

interface TransitionConfig {
target: State;
guard?: (ctx: any, event: Event) => boolean;
actions?: Array<(ctx: any, event: Event) => any>;
}

interface StateConfig {
on?: Record<string, TransitionConfig | TransitionConfig[]>;
entry?: Array<(ctx: any, event: Event) => any>;
exit?: Array<(ctx: any, event: Event) => any>;
}

interface MachineConfig {
initial: State;
context: Record<string, any>;
states: Record<string, StateConfig>;
}

class StateMachine {
private current: State;
private context: Record<string, any>;
private config: MachineConfig;
private listeners: Array<(state: State, context: any) => void> = [];

constructor(config: MachineConfig) {
this.config = config;
this.current = config.initial;
this.context = { ...config.context };
}

get state() { return this.current; }
get ctx() { return this.context; }

subscribe(listener: (state: State, context: any) => void) {
this.listeners.push(listener);
return () => {
this.listeners = this.listeners.filter(l => l !== listener);
};
}

send(event: Event): void {
const stateConfig = this.config.states[this.current];
if (!stateConfig?.on?.[event.type]) return;

const transitions = Array.isArray(stateConfig.on[event.type])
? stateConfig.on[event.type]
: [stateConfig.on[event.type]];

for (const transition of transitions) {
if (transition.guard && !transition.guard(this.context, event)) continue;

// 执行 exit 动作
stateConfig.exit?.forEach(action => action(this.context, event));

// 执行转换动作
transition.actions?.forEach(action => action(this.context, event));

// 切换状态
const prevState = this.current;
this.current = transition.target;

// 执行 entry 动作
const newStateConfig = this.config.states[this.current];
newStateConfig?.entry?.forEach(action => action(this.context, event));

// 通知监听者
this.listeners.forEach(l => l(this.current, this.context));

console.log(`[${prevState}] --${event.type}--> [${this.current}]`);
return; // 只执行第一个匹配的转换
}
}
}

// 使用示例
const trafficLight = new StateMachine({
initial: 'red',
context: { cycleCount: 0 },
states: {
red: {
entry: [(ctx) => console.log('🚗 停车!')],
on: {
TIMER: { target: 'green' }
}
},
green: {
entry: [(ctx) => console.log('🚗 通行!')],
on: {
TIMER: { target: 'yellow' }
}
},
yellow: {
entry: [(ctx, e) => { ctx.cycleCount++ }],
on: {
TIMER: {
target: 'red',
guard: (ctx) => ctx.cycleCount < 5,
// 循环 5 次后...(此处无匹配则停留在 yellow)
}
}
}
}
});

trafficLight.send({ type: 'TIMER' }); // red -> green
trafficLight.send({ type: 'TIMER' }); // green -> yellow
trafficLight.send({ type: 'TIMER' }); // yellow -> red
// ...循环往复

这个简易实现涵盖了状态机的核心要素:状态、事件、转换、守卫条件、动作和上下文。生产级框架在此基础上还会增加层次化状态、并行状态、延迟转换、Actor 模型等高级特性。

10 总结

事件驱动的状态机框架是一种经过时间考验的架构模式,其核心优势在于:

  1. 确定性:相同输入总是产生相同输出,便于测试和验证
  2. 可视化:状态图天然是文档,降低沟通成本
  3. 防御性编程:通过消除"不可能状态"减少 bug
  4. 可扩展性:从简单 FSM 到 Statecharts,可以渐进式增加复杂度
  5. 跨领域通用:从前端到嵌入式,从游戏到网络协议,同一套思维模型

无论你选择 XState、transitions、QP 还是自研框架,理解其背后的核心原理——有限状态、事件驱动、确定性转换——都能帮助你构建更健壮、更可维护的软件系统。

参考资料

文章链接:
https://www.zywvvd.com/notes/coding/design-pattern/event-driven-state-machine/event-driven-state-machine/