什么是状态机
状态机是有限状态自动机的简称,是现实事物运行规则抽象而成的一个数学模型。英文名字叫State Machine ,不是指一台实际机器,一般就是指一张状态转换图。全称是有限状态自动机,自动两个字包含重要含义。给定一个状态机,同时给定它的当前状态以及输入,那么输出状态时可以明确的运算出来的,当输入条件时,能输出下一个状态。
现实事物是有不同状态,例如一个LED等,就有 亮 和 灭两种状态。我们通常所说的状态机是有限状态机,也就是被描述的事物的状态的数量是有限个,例如LED灯的状态就是两个亮和 灭。
为什么用状态机
状态机解决的问题就是当某种模型的状态变更比较比较复杂,且状态比较多,那么我们有必要将这些状态变化的逻辑抽象出来,做成一个可以统一调用的算法,这样封装出来的代码就比较好维护,同时可读性也很强。
状态机在实际工作开发中很有用,应用也非常广泛。一个健壮的状态机可以让你的程序,不论发生何种突发事件都不会突然进入一个不可预知的程序分支,可以很清晰的表达整个状态的流转。
在GUI应用程序、Web应用程序等事件驱动型的应用程序,采用状态机的思路来完成程序设计,可以简化设计流程,使程序的可读性、可维护性都得到增加。
使用状态机有哪些好处?
1. 当一个程序有多个状态时,规范了状态机的状态转换,避免了一些引入一些复杂的判断逻辑。
2. 规范了程序在不同状态下所能提供的能力。
3. 在能力上可以进行横向扩展,提供新的状态来完善现有逻辑。
简单状态机的实现
使用switch跳转即可实现一种简单的状态机。如果逻辑不是很复杂,使用switch语句也能达到实现目的。举例如下:
enum state { nullState_, firstState_, secondState_, thirdState_, quitState_, }; struct param_t { int param1; int param2; // ...... }; int nullStateProc(param_t& param){ return firstState_; } int firstStateProc(param_t& param){ return secondState_; } int secondStateProc(param_t& param){ return secondState_; } int thirdStateProc(param_t& param){ return secondState_; } int quitEvent(){ return nullState_; } int stateMachine(state& state_, param_t& param){ switch (state_) { case nullState_: state_ = static_cast<state>(nullStateProc(param)); break; case firstState_: state_ = static_cast<state>(firstStateProc(param)); break; case secondState_: state_ = static_cast<state>(secondStateProc(param)); break; case thirdState_: state_ = static_cast<state>(thirdStateProc(param)); break; case quitState_: quitEvent(); return 0; } return 1; } void start() { state state_ = nullState_; param_t param{}; while (true) { auto stateResult = stateMachine(state_, param); if (!stateResult) { return; } } }
另一种简单实现
如果需要管理的状态和事件比较多,需要逻辑清晰和便于维护,使用简单的switch可能无法满足需求。这里介绍一种简单的实现,消除庞大的条件分支语句,从配置表容易看出各个状态的转换图。
/* statemachine.h*/ #ifndef _STATEMACHINE_H_ #define _STATEMACHINE_H_ #include <iostream> enum EventActionResult { EventFailed, EventProcessedOK }; template<class T, class P> class State { public: std::string inputEvent; State<T, P> *nextState; EventActionResult (T::*action)(const std::string &event, P *param); State<T, P> *errorState; }; template<class T, class P> class StateMachine { private: State<T, P> *init; State<T, P> *current; T *target; public: StateMachine() {} void Init(T *_target, State<T, P> *initialState) { init = current = initialState; target = _target; } void Reset() { current = init; } void ProcessEvent(const std::string &event, P *param) { for (State<T, P> *p = this->current; p->nextState != NULL; p++) { if (p->inputEvent == event) { if (p->action != NULL) { if (EventFailed == (this->target->*(p->action))(event, param)) { if (p->errorState != NULL) { //Only if there's an errorstate defined. Otherwise, just do nothing this->current = p->errorState; } return; } } this->current = p->nextState; return; } } //Event not found. Do nothing return; } };
以下是使用举例:
class MyStateMachine { public: struct param_t { int param; int param1; int param2; }; StateMachine<MyStateMachine, param_t> stMachine; EventActionResult HandleEvent1(const std::string &e, param_t *param); EventActionResult HandleEvent2(const std::string &e, param_t *param); EventActionResult HandleEventA(const std::string &e, param_t *param); EventActionResult HandleEventB(const std::string &e, param_t *param); EventActionResult HandleThree(const std::string &e, param_t *param); void HandleEvent(const std::string &e, param_t *param); void Init(); void Start(); };
#include "statemachine.h" #include <cstdlib> #include <iostream> #include <stdio.h> typedef State<MyStateMachine, MyStateMachine::param_t> STATE; extern STATE Idle[]; extern STATE One[]; extern STATE Two[]; STATE Idle[] = { //EVENT,NEXT, ACTION, ERRORSTATE (where to land if there's an error) {"event1", One, &MyStateMachine::HandleEvent1, Idle}, {"event2", Two, &MyStateMachine::HandleEvent2, Idle}, {"", NULL, NULL, NULL}, //End of table }; STATE One[] = { {"eventA", Idle, &MyStateMachine::HandleEventA, Idle}, {"eventB", Idle, &MyStateMachine::HandleEventB, Idle}, {"", NULL, NULL, NULL}, }; STATE Two[] = { {"eventC", Idle, NULL, NULL}, {"", NULL, NULL, NULL}, }; EventActionResult MyStateMachine::HandleEvent1(const std::string &e, param_t *param) { std::cout << "HandleEvent1,param:" << param->param << std::endl; return EventProcessedOK; } EventActionResult MyStateMachine::HandleEvent2(const std::string &e, param_t *param) { std::cout << "HandleEvent2,param:" << param->param << std::endl; return EventProcessedOK; } EventActionResult MyStateMachine::HandleEventA(const std::string &e, param_t *param) { std::cout << "HandleEventA,param:" << param->param << std::endl; return EventProcessedOK; } EventActionResult MyStateMachine::HandleEventB(const std::string &e, param_t *param) { std::cout << "HandleEventB,param:" << param->param << std::endl; return EventProcessedOK; } EventActionResult MyStateMachine::HandleThree(const std::string &e, param_t *param) { std::cout << "HandleThree" << std::endl; return EventProcessedOK; } void MyStateMachine::HandleEvent(const std::string &e, param_t *param) { stMachine.ProcessEvent(e, param); } void MyStateMachine::Init() { stMachine.Init(this, Idle); } void MyStateMachine::Start() { while (1) { char c[255]; // 模拟输入event std::cin.getline(c,255); std::string event{c}; MyStateMachine::param_t param; param.param = 1; this->HandleEvent(event, ¶m); } }
以上示例在单线程下没问题,但若考虑多线程并发,考虑到当一个跳转正在进行的时候,同时又有其他任务请求跳转,则可能会出现数据不一致的问题。
举个例子:task1(s1, c1/a1 –> s2) 和 task2(s2, c2/a2 –> s3) 先后执行,是可以顺利到达s3状态的,但若操作a1运行的时候,执行权限被task2抢占,则task2此时看到的当前状态还是s1,s1遇到c2就进入陷阱状态,而不会到达s3了,也就是说,状态的跳转发生了不确定,这是不能容忍的。
因此要重新设计状态机,用上队列,把触发事件加入到队列中依次出队和执行。
#include <condition_variable> #include <mutex> #include <queue> /** Multiple producer, multiple consumer thread safe queue * Since 'return by reference' is used this queue won't throw */ template <typename T> class shared_queue { std::queue<T> queue_; mutable std::mutex m_; std::condition_variable data_cond_; public: shared_queue() = default; shared_queue &operator=(const shared_queue &) = delete; shared_queue(const shared_queue &other) = delete; void push(T item) { { std::lock_guard<std::mutex> lock(m_); queue_.push(std::move(item)); } data_cond_.notify_one(); } /// Try to retrieve, if no items, wait till an item is available and try again void wait_and_pop(T &popped_item) { std::unique_lock<std::mutex> lock(m_); while (queue_.empty()) { data_cond_.wait(lock); // This 'while' loop is equal to // data_cond_.wait(lock, [](bool result){return !queue_.empty();}); } popped_item = std::move(queue_.front()); queue_.pop(); } bool wait_and_pop_timed(T &popped_item, std::int64_t duration) { std::unique_lock<std::mutex> lock(m_); while (queue_.empty()) { auto timeout = data_cond_.wait_for(lock, std::chrono::microseconds(duration)); if (timeout == std::cv_status::timeout) { return true; } } popped_item = std::move(queue_.front()); queue_.pop(); return false; } };
引用
什么是状态机?_pingxiaozhao的博客-CSDN博客_状态机的概念
为Linux应用构造有限状态机_wowocpp的博客-CSDN博客_linux 状态机
有限状态机详解(转载)_白小狮的博客-CSDN博客_有限状态机和无限状态机
为Linux操作系统应用构造有限状态机方法-红联Linux系统门户
一文详解 Android状态机StateMachine 使用方式及实现原理_bjxiaxueliang的博客-CSDN博客_statemachine
c++写状态机_zhi_cary的博客-CSDN博客_c++ 状态机
C++有限状态机的实现_Valreaper的博客-CSDN博客_c++ 状态机
github经典C++状态机(fsm)源代码剖析_star-keke的博客-CSDN博客
用C++来实现有限状态机(附代码)_李肖遥的博客-CSDN博客
状态模式(state)C++实现_shu_chang1993的博客-CSDN博客_c++state