有限状态机(问什么从来没有提过无限状态机,请知道的朋友不吝赐教)的官方文档定义是:
State(S) x Event(E) -> Actions(A), State(S')
If we are in state S and the event E occurs, we should perform the actions A and make a transition to the state S'.
或者用下面的表理解:
条件/当前状态 |
状态A |
状态B |
状态C |
条件X |
... |
... |
... |
条件Y |
... |
状态C |
... |
条件Z |
状态B |
... |
状态A |
如何写一个gen_fms呢,下面以例子来讲诉。
1. 假设一个人有这么3种状态(健康,生病和康复中),以及对应的转换事件(淋雨,治疗和休息)
2. 定义状态名分别为health,sick和rehabilitationing,事件分别为rain,treatment和一个特殊的timeout。每种状态下,不需要额外的状态数据。
3. 按照gen_fsm规定,状态转换规则是以状态名同名的回调函数,格式为:
StateName(Event, StateData) ->
.. code for actions here ...
{next_state, StateName', StateData'}
所以在这个例子里,我们写上3个状态处理回调函数:
%% 健康状态下淋雨,变为生病状态 health( rain, StateData ) -> { next_state, sick, StateData }. %% 生病状态下被治疗,进入康复中状态 sick( treatment, StateData ) -> { next_state, rehabilitationing, StateData, 30000 }. %% 30秒后有timeout事件 %% 康复中状态下,超时事件发生,进入健康状态 rehabilitationing( timeout, StateData ) -> { next_state, health, StateData }.
每个状态处理回调函数的返回值必须是{next_state, 新状态名,新状态对应的状态数据},或者{next_state, 新状态名,新状态对应的状态数据, Timeout时间}。(还可以用{stop, Reason, StateData}作为返回值,来告知gem_fsm进程结束)
4. 我们还需要一个初始状态,在init回调设置初始状态
init(_Param) -> {ok, health, {} }.
5. 为模块写事件触发API,用gen_fsm:send_event通知gen_fsm事件发生(事件不一定是atom,可以是任意的erlang类型)。
rain() -> gen_fsm:send_event( ?MODULE, rain ). treatment() -> gen_fsm:send_event( ?MODULE, treatment ).
6. 加上启动API和指定行为模式,我们的gen_fsm例子就算大功告成了。全模块代码如下:
-module(person_fsm). -behaviour(gen_fsm). -export([start_link/0, init/1]). -export([rain/0, treatment/0]). -export([health/2,sick/2,rehabilitationing/2]). start_link() -> gen_fsm:start_link( {local,?MODULE}, ?MODULE, [], [] ). init([]) -> io:format("initial state is health.~n"), {ok, health, {} }. rain() -> gen_fsm:send_event( ?MODULE, rain ). treatment() -> gen_fsm:send_event( ?MODULE, treatment ). %% finite state machines health( rain, {} ) -> io:format("turn to state sick.~n"), { next_state, sick, {} }. sick( treatment, _StateData ) -> io:format("turn to state rehabilitationing.~n"), { next_state, rehabilitationing, { }, 30000 }. rehabilitationing( timeout, _StateData ) -> io:format("turn to state health.~n"), { next_state, health, {} }.
gen_fsm:start_link在此不细讲了,如果不清楚的参见原官方文档。
gen_server和gen_fsm比较:
1. gen_server的状态对应为gen_fsm的一个状态名和状态数据。
2. gen_server在handle_cast里处理异步消息/事件,gen_fsm在状态名同名的回调处理异步事件。
3. gen_fsm实现的例子完全可以用gen_server来实现。