状态idle什么原因_C语⾔中的状态机设计
本⽂不是关于软件状态机的最佳设计分解实践的教程。我将重点关注状态机代码和简单的⽰例,这些⽰例具有⾜够的复杂性,以便于理解特性和⽤法。
背景
⼤多数程序员常⽤的设计技术是有限状态机(FSM)。设计⼈员使⽤此编程结构将复杂的问题分解为可管理的状态和状态转换。有⽆数种实现状态机的⽅法。
A switch语句提供了状态机最容易实现和最常见的版本之⼀。在这⾥,每个案例在switch语句成为⼀个状态,实现如下所⽰:
藏 复制码
switch (currentState) {乐加乐英语
ca ST_IDLE:
// do something in the idle state
break;
ca ST_STOP:
// do something in the stop state
break;
//
}
这种⽅法当然适合于解决许多不同的设计问题。然⽽,在事件驱动的多线程项⽬上使⽤时,这种形式的状态机可能是⾮常有限的。
第⼀个问题是控制哪些状态转换是有效的,哪些是⽆效的。⽆法强制执⾏状态转换规则。任何过渡都可以在任何时候进⾏,这并不是特别可取的。对于⼤多数设计,只有少数转换模式是有效的。理想情况下,软件设计应该强制执⾏这些预定义的状态序列,并防⽌不必要的转换。当试图将数据发送到特定状态时,会出现另⼀个问题。由于整个状态机位于单个函数中,因此向任何给定状态发送额外数据都是困难的。最后,这些设计很少适合在多线程系统中使⽤。设计器必须确保状态机是从单个控制线程调⽤的。
为什么要⽤国家机器?
使⽤状态机实现代码是解决复杂⼯程问题的⼀种⾮常⽅便的设计技术。状态机将设计分解为⼀系列步骤,或在状态机术语中称为状态。每个状态都执⾏⼀些狭义的任务。另⼀⽅⾯,事件是⼀种刺激,它导致状态机在状态之间移动或过渡。
举⼀个简单的例⼦,我将在本⽂中使⽤它,假设我们正在设计电机控制软件。我们想启动和停⽌电机,以及改变电机的速度。很简单。向客户端软件公开的电机控制事件如下:英语作文自我介绍
1. 设定速度
设定速度-设定电机以特定速度⾏驶
站住-停⽌马达
2. 站住
这些事件提供了以任何速度启动电机的能⼒,这也意味着改变已经移动的电机的速度。或者我们可以完全停⽌马达。对于电机控制模块,这两个事件或功能被认为是外部事件.然⽽,对于使⽤我们的代码的客户机来说,这些只是普通的函数。
这些事件不是状态机状态。处理这两个事件所需的步骤是不同的。在这种情况下,各州是:
闲散-马达不是旋转的,⽽是静⽌的
1. 闲散
⽆所事事
启动-从死胡同启动马达
1. 启动
开启电动机电源
设定电机转速
变速-调整已经移动的马达的速度
1. 变速
改变电机转速
1. 停-停⽌移动的马达
关闭电动机电源
进⼊闲置状态
可以看出,将电机控制分解为离散状态,⽽不是单⼀的功能,我们可以更容易地管理如何操作电机的规则。
每个状态机都有“当前状态”的概念。这是状态机当前所处的状态。在任何给定的时刻,状态机只能处于单⼀状态。特定状态机实例的每个实例在定义时都可以设置初始状态。但是,该初始状态在对象创建期间不执⾏。只有发送到状态机的事件才会导致执⾏状态函数。
为了图形化地说明状态和事件,我们使⽤状态图。下⾯的图1显⽰了电机控制模块的状态转换。框表⽰状态,连接箭头表⽰事件转换。列出事件名称的箭头是外部事件,⽽未装饰的⾏被认为是内部事件。(本⽂后⾯将介绍内部事件和外部事件之间的差异。)
图1:电机状态图
如您所见,当事件在状态转换中出现时,所发⽣的状态转换取决于状态机的当前状态。当SetSpeed事件出现,例如,电机在Idle状态,则转换为Start状态。然⽽,同样的SetSpeed当前状态为Start将电机转换为ChangeSpeed状态。您还可以看到,并⾮所有的状态转换都是有效的。例如,马达不能从ChangeSpeed到Idle⽽不需要先通过Stop状态。
简⽽⾔之,使⽤状态机捕获和执⾏复杂的交互,否则可能很难传递和实现。
内外事件
正如我前⾯提到的,事件是导致状态机在状态之间转换的刺激。例如,按下按钮可能是⼀个事件。事件可以分为两类:外部事件和内部事件。外部事件,在其最基本的级别上,是对状态机模块的函数调⽤.这些函数是公共的,从外部调⽤,或者从外部代码调⽤到状态机对象。系统中的任何线程或任务都可以⽣成外部事件。如果外部事件函数调⽤导致状态转换发⽣,则状态将在调⽤⽅的控制线程内同步执⾏。另⼀⽅⾯,内部事件是由状态机本⾝在状态执⾏期间⾃⾏⽣成的。
典型的场景由⽣成的外部事件组成,该事件同样可以归结为模块的公共接⼝中的函数调⽤。根据正在⽣成的事件和状态机的当前状态,执⾏查找以确定是否需要转换。如果是这样,状态机将转换到新状态,并执⾏该状态的代码。在状态函数的末尾,执⾏检查以确定是否⽣成了内部事件。如果是这样,则执⾏另⼀个转换,并且新的状态有机会执⾏。此过程将继续进⾏,直到状态机不再⽣成内部事件,此时原始外部事件函数调⽤将返回。外部事件和所有内部事件(如果有的话)在调⽤者的控制线程中执⾏。
⼀旦外部事件启动状态机执⾏,它不能被另⼀个外部事件中断,直到外部事件和所有内部事件已经完成执⾏,如果使⽤锁。这个运⾏到完成模型为状态转换提供了⼀个多线程安全的环境。可以在状态机
引擎中使⽤信号量或互斥量来阻⽌可能同时访问同⼀状态机实例的其他线程。见源代码函数_SM_ExternalEvent()关于锁的位置的注释。
事件数据
⽣成事件时,它可以选择附加事件数据,以便在执⾏过程中由状态函数使⽤。事件数据是⼀个const或者不是-const 指向任何内置或⽤户定义的数据类型的指针。
⼀旦状态完成执⾏,事件数据就被认为⽤完了,必须删除。因此,发送到状态机的任何事件数据都必须通过SM_XAlloc()。状态机引擎⾃动释放分配的事件数据。SM_XFree().
状态转变
当⽣成外部事件时,执⾏查找以确定状态转换操作过程。事件有三种可能的结果:新状态、忽略事件或不能发⽣。新状态会导致转换到允许执⾏的新状态。转换到现有状态也是可能的,这意味着当前状态被重新执⾏。对于被忽略的事件,不执⾏任何状态。但是,事件数据(如果有的话)将被删除。最后⼀种不可能发⽣的可能性是保留在事件在状态机的当前状态下⽆效的情况下使⽤的。如果发⽣这种情况,软件就会出现故障。
在此实现中,执⾏验证转换查找不需要内部事件。假设状态转换是有效的。您可以检查有效的内部和
外部事件转换,但实际上,这只会占⽤更多的存储空间,并且只会产⽣很少的好处。验证转换的真正需要在于异步的外部事件,在这些事件中,客户端可能导致事件在不适当的时间发⽣。⼀旦状态机执⾏,它就不能被中断。它处于私有实现的控制之下,因此没有必要进⾏转换检查。这使设计⼈员可以⾃由地通过内部事件更改状态,⽽⽆需更新转换表。
状态机模块
状态机源代码包含在StateMachine.c和StateMachine.h档案。下⾯的代码显⽰了部分标题。这个StateMachine 报头包含各种预处理器多⾏宏,以简化状态机的实现。
藏 收缩
复制码
enum { EVENT_IGNORED = 0xFE, CANNOT_HAPPEN = 0xFF };
typedef void NoEventData;
// State machine constant data
typedef struct
{
const CHAR* name;
const BYTE maxStates;
const struct SM_StateStruct* stateMap;
const struct SM_StateStructEx* stateMapEx;
} SM_StateMachineConst;
// State machine instance data
typedef struct
{
const CHAR* name;
void* pInstance;
BYTE newState;
BYTE currentState;
BOOL eventGenerated;
void* pEventData;
} SM_StateMachine;
// Generic state function signatures
typedef void (*SM_StateFunc)(SM_StateMachine* lf, void* pEventData);
typedef BOOL (*SM_GuardFunc)(SM_StateMachine* lf, void* pEventData);
typedef void (*SM_EntryFunc)(SM_StateMachine* lf, void* pEventData);
typedef void (*SM_ExitFunc)(SM_StateMachine* lf);
typedef struct SM_StateStruct
{
SM_StateFunc pStateFunc;
} SM_StateStruct;
typedef struct SM_StateStructEx
{
SM_StateFunc pStateFunc;
SM_GuardFunc pGuardFunc;
SM_GuardFunc pGuardFunc;
SM_EntryFunc pEntryFunc;
SM_ExitFunc pExitFunc;
} SM_StateStructEx;gagman
// Public functions
#define SM_Event(_smName_, _eventFunc_, _eventData_)
_eventFunc_(&_smName_##Obj, _eventData_)
// Protected functions
#define SM_InternalEvent(_newState_, _eventData_)
_SM_InternalEvent(lf, _newState_, _eventData_)
#define SM_GetInstance(_instance_)
(_instance_*)(lf->pInstance);
// Private functions
void _SM_ExternalEvent(SM_StateMachine* lf,
const SM_StateMachineConst* lfConst, BYTE newState, void* pEventData);
void _SM_InternalEvent(SM_StateMachine* lf, BYTE newState, void* pEventData);
void _SM_StateEngine(SM_StateMachine* lf, const SM_StateMachineConst* lfConst);wheneverwherever
void _SM_StateEngineEx(SM_StateMachine* lf, const SM_StateMachineConst* lfConst);
#define SM_DECLARE(_smName_)
extern SM_StateMachine _smName_##Obj;
欺侮#define SM_DEFINE(_smName_, _instance_)
SM_StateMachine _smName_##Obj = { #_smName_, _instance_,
0, 0, 0, 0 };
#define EVENT_DECLARE(_eventFunc_, _eventData_)
void _eventFunc_(SM_StateMachine* lf, _eventData_* pEventData);
pooling#define EVENT_DEFINE(_eventFunc_, _eventData_)
void _eventFunc_(SM_StateMachine* lf, _eventData_* pEventData)
#define STATE_DECLARE(_stateFunc_, _eventData_)
static void ST_##_stateFunc_(SM_StateMachine* lf, _eventData_* pEventData);
#define STATE_DEFINE(_stateFunc_, _eventData_)
static void ST_##_stateFunc_(SM_StateMachine* lf, _eventData_* pEventData)
这个SM_Event()宏⽤于⽣成外部事件,⽽SM_InternalEvent()在执⾏状态函数期间⽣成内部事件。SM_GetInstance()获取指向当前状态机对象的指针。
SM_DECLARE 和SM_DEFINE⽤于创建状态机实例。EVENT_DECLARE和EVENT_DEFINE创建外部事件函数。最
后,STATE_DECLARE和STATE_DEFINE创建状态函数。
电机实例ballantine
Motor 实现我们假设的电机控制状态机,其中客户端可以启动电机,以特定的速度,并停⽌电机。这个Motor标题接⼝如下所⽰:
藏 复制码
#include "StateMachine.h"
doublekill// Motor object structure
typedef struct
{
INT currentSpeed;
} Motor;
// Event data structure
typedef struct
{
INT speed;
} MotorData;
// State machine event functions
EVENT_DECLARE(MTR_SetSpeed, MotorData)bsr
EVENT_DECLARE(MTR_Halt, NoEventData)
这个Motor源⽂件使⽤宏通过隐藏所需的状态机机器来简化使⽤。藏 收缩
topman复制码