可重入FSM有四种实现路径:一、线程局部状态栈,分离逻辑与存储;二、原子变量+CAS无锁更新;三、纯const函数+状态参数化,完全无状态;四、RAII递归锁包装,支持嵌套但性能较低。

如果您希望在多线程环境中安全使用有限状态机,且要求同一 FSM 实例能被多个线程并发调用而不产生数据竞争或状态错乱,则必须确保其可重入性。以下是实现可重入 FSM 的关键设计路径:
一、基于线程局部状态栈的 FSM 封装
该方法将状态转移逻辑与状态存储分离,每个线程持有独立的状态快照,避免共享状态变量引发的竞争条件。FSM 核心类仅提供状态迁移规则和事件分发接口,实际状态保存在线程局部存储中。
1、定义状态枚举类型 StateEnum,并声明无状态的纯虚基类 IStateTransition,包含 onEvent() 接口但不维护 state_ 成员变量。
2、在 FSM 类中声明 thread_local static std::unordered_map
立即学习“C++免费学习笔记(深入)”;
3、在 handleEvent() 成员函数开头,通过 const void* key = static_cast
4、执行状态转移后,将新状态写回 s_threadStateMap[key],确保下次同一线程调用时复用该状态。
二、使用原子状态变量与无锁状态更新
该方法采用 std::atomic
1、在 FSM 类中声明 private: std::atomic
2、在 onEvent() 函数内,以 do-while 循环封装状态读取与 CAS 更新:先 load 当前状态,再根据事件类型查表获得目标状态 next,最后调用 m_state.compare_exchange_weak(current, next)。
3、若 compare_exchange_weak 返回 false,说明状态已被其他线程修改,循环重试直至成功或判定不可迁移(如事件不被当前状态接受)。
4、所有状态处理逻辑(如 enter/exit 动作)必须是纯函数或可重入函数,禁止访问非 const 成员变量或静态可变数据。
三、基于 const 成员函数与状态参数化的 FSM
该方法彻底消除内部可变状态,将当前状态作为显式参数传入所有迁移函数,使 FSM 对象本身成为无状态的纯行为容器,天然满足可重入性要求。
1、将 FSM 类的所有核心接口(如 transitOnInput、canHandleEvent)声明为 const 成员函数,并接收 StateEnum current_state 和 EventType event 作为输入参数。
2、定义私有 const 成员:std::array<:array num_events>, NUM_STATES> m_transitionTable,编译期初始化,只读不可变。
3、transitOnInput() 内部仅做查表操作:return m_transitionTable[current_state][event]; 不修改任何成员变量。
4、外部调用者负责维护自身状态变量,每次调用 FSM 方法时传入当前状态,并用返回值更新其本地状态;调用者需保证传入的状态合法且未被并发修改。
四、RAII 包装器配合递归互斥量控制重入边界
该方法允许 FSM 内部保留可变状态,但通过 RAII 锁包装器限制同一对象在同一时刻仅有一个线程处于状态迁移临界区,同时支持同一线程的嵌套调用。
1、在 FSM 类中声明 mutable std::recursive_mutex m_mutex;并定义私有辅助结构 LockGuard { explicit LockGuard(FSM& f) : fsm_(f) { fsm_.m_mutex.lock(); } ~LockGuard() { fsm_.m_mutex.unlock(); } FSM& fsm_; }。
2、所有可能修改内部状态的方法(如 handleEvent、setState)开头构造 LockGuard guard(*this),确保进入时加锁、退出时自动释放。
3、将原 state_ 成员变量改为 mutable StateEnum m_state,使其可在 const 成员函数中被 LockGuard 保护下修改。
4、注意:此方案虽支持重入,但性能低于无锁方案,且无法防止跨线程状态观察不一致;仅推荐用于已有不可重构 FSM 的渐进式改造。


















