状态机顾名思义就是指包含状态的机器,在不同的状态下,状态机允许执行不同的消息(不允许执行的未在当前状态定义执行过程的消息直接忽略执行下一消息),在计算机内将状态机和状态都定义为类,因此,模拟状态机就是指在一个类中包含状态类表变量,以及当前状态指针变量,作者将状态机分为以下两种:
- 同时处于两种状态的状态机,该状态机在作者制作操作系统的时候使用,在该操作系统中同时存在KCB和PCB两个状态,其组织关系类似与用户与记事本的管理,对于操作系统始终保持KCB的状态,但是有PCB消息时,也会处理PCB消息。
- 同时只存在一个状态的状态机,但状态机本身派生自状态类,这种状态机任意时刻只处于一种状态,但是部分调度信息(包括状态进入,退出、开始、暂停、停止)由状态或者用户发消息给状态机本身处理,这种状态机作者广泛应用与各个项目。
大家可以参考一本状态机相关书籍(书名:Practical UML Statecharts in C/C++, 2nd Ed Event-Driven Programming for Embedded Systems(嵌入式系统的微模块化程序设计),第一版有中文翻译出版但翻译的很烂,第二版只有英文(中文版本网络翻译可以通过以下网址下载:https://www.state-machine.com/psicc2,该网站也是MIRO SAMEK的QP(即Quantum Programming)量子化编程技术的官方网站)且增加了许多额外的作者认为无伤大雅的功能,看起来比较复杂,),作者在此获益良多,但是,作者认为该书作者对状态机的实现较为复杂(以人为主(非以己为主),格局小了),换句话说,需求是扩散而非收敛(目前西方软件的状态),作者使用了额外一个并行的状态用于状态机的调度,简化了状态机的实现,且使状态机具有一定的主观能动性。
作者在当前项目中实现的是对第二种状态机的模拟,其通用定义保存在base工程中,特定的项目定义保存在logic工程中,状态机变量保存在system类全局变量中,其通用定义使用C++编程如下:
状态的定义
#ifndef V_STATE_H
#define V_STATE_H
#include "variant.h"
#include "v_msgqueue.h"
#include <QMutex>
class V_StateMachine;
class V_State : public Variant
{
public:
V_State();
V_State(int iID,V_StateMachine* pParent);
V_MsgQueue m_VarMsgQueue;
V_StateMachine* m_pParent;
public:
QMutex mutex;
void pushback_Msg(const V_Msg& msg);
void pushfront_Msg(const V_Msg& msg);
V_Msg popfront_Msg();
void clear_Msg();
public:
virtual void do_Msg(const V_Msg& msg);
};
#endif // V_STATE_H
使用事件锁保证多线程消息队列操作的原子性,状态机主要调用状态的消息处理函数,因此使用事件锁的场景是有限的,收敛的,每一个状态都是一个包含消息缓冲队列和消息处理函数的类,因此,作者额外定义了消息类和消息队列的类用于集中操作消息队列,消息类定义的代码如下:
消息类的定义
#ifndef V_MSG_H
#define V_MSG_H
#include "variant.h"
class V_Msg : public Variant
{
public:
V_Msg(int iID);
void* m_pInput;
void** m_ppOutput;
private:
void Init();
};
#endif // V_MSG_H
作者使用C指针保存消息输入输出起始地址信息,同时作者规定,ID大于0的消息是可以由消息处理方面释放的,而ID小于0的消息是不能够作为堆内存释放的,0号消息作为保留值以判断消息的有效性。
消息队列类的定义
#ifndef V_MSGQUEUE_H
#define V_MSGQUEUE_H
#include "variant.h"
#include "v_msg.h"
#include <QList>
typedef QList<V_Msg> LVMSG;
class V_MsgQueue : public Variant
{
public:
V_MsgQueue();
LVMSG m_lVarMsg;
public:
};
#endif // V_MSGQUEUE_H
作者额外定义消息队列类主要是希望消息队列的处理能够集中进行。
状态机的定义
#ifndef V_STATEMACHINE_H
#define V_STATEMACHINE_H
#include "variant.h"
#include "variants.h"
#include "v_state.h"
#include <QMap>
typedef QMap<int, V_State*> MVarState;
class V_StateMachine : public V_State
{
public:
V_StateMachine();
V_StateMachine(int iID,const QString& sName);
virtual ~V_StateMachine();
Variants m_Variants;
MVarState m_mMVarState;
V_State *get_VarState(int iStateID);
int m_iStateID;
virtual V_Msg run();
void WriteConfig(QDomDocument &doc, QDomElement &root);
void ReadConfig(QDomElement &root);
void do_Msg(const V_Msg& msg);
protected:
virtual void Init();
};
QDataStream &operator<<(QDataStream &out,V_StateMachine& varSM);
QDataStream &operator>>(QDataStream &in, V_StateMachine& varSM);
#endif // V_STATEMACHINE_H
状态机的主要作用是执行当前状态中的消息函数处理当前状态的消息队列,在状态机的初始化函数中默认初始化三个状态(Idle,Pause,Work)(在派生类未初始化时(查找不到相关状态),进行默认初始化,换句话说,作者实现的状态机至少包含三种状态),注意当前状态机的run函数并不是循环执行的,在下一篇的logic项目中的带线程的状态机,才在计算机中无限循环执行消息处理函数。
另外作者重写了do_Msg(派生自状态类,用于状态调度)消息处理函数,在run函数中,先调用状态机本身的do_Msg消息处理函数,然后再调用当前状态的do_Msg消息处理函数,在消息处理的原子过程中,如果出现错误,则使用C语言的try-catch机制捕捉错误,并再非idle状态下,将消息再次填入消息队列,并切换至ilde暂停状态,这是作者在所有项目中的错误处理的原则,相关实现如下:
run函数:
V_Msg V_StateMachine::run()
{
V_Msg msgCore= popfront_Msg();
do_Msg(msgCore);
V_State *pState=get_VarState(m_iStateID);
Q_ASSERT(pState);
V_Msg msg= pState->popfront_Msg();
if(msg.m_iID==0)
{
return msg;
}
try
{
pState->do_Msg(msg);
}
catch(QString &s)
{
if(m_iStateID==VarState_Work_ID)
{
pState->pushfront_Msg(msg);
m_iStateID=VarState_Pause_ID;
}
else
{
pState->clear_Msg();
}
throw(s);
}
return msg;
}
状态机的do_Msg消息处理函数:
void V_StateMachine::do_Msg(const V_Msg &msg)
{
switch(msg.m_iID)
{
case VarMsg_Start_ID:
{
if(m_iStateID==VarState_Idle_ID||
m_iStateID==VarState_Pause_ID)
{
m_iStateID=VarState_Work_ID;
}
break;
}
case VarMsg_Pause_ID:
{
if(m_iStateID==VarState_Work_ID)
{
m_iStateID=VarState_Pause_ID;
}
break;
}
case VarMsg_Stop_ID:
{
m_iStateID=VarState_Idle_ID;
break;
}
}
V_State::do_Msg(msg);
}
D7FECB19D148C1AD1D829F1CD0E23DB0
发表回复
要发表评论,您必须先登录。