Chinaunix首页 | 论坛 | 博客
  • 博客访问: 5120017
  • 博文数量: 921
  • 博客积分: 16037
  • 博客等级: 上将
  • 技术积分: 8469
  • 用 户 组: 普通用户
  • 注册时间: 2006-04-05 02:08
文章分类

全部博文(921)

文章存档

2020年(1)

2019年(3)

2018年(3)

2017年(6)

2016年(47)

2015年(72)

2014年(25)

2013年(72)

2012年(125)

2011年(182)

2010年(42)

2009年(14)

2008年(85)

2007年(89)

2006年(155)

分类: Python/Ruby

2012-10-22 11:50:16

今天介绍erlang的一个非常重要的behaviour,就是gen_fsm-有限状态机,有限状态机的作用非常之多,比如文本解析,模式匹配、游戏逻辑等等方面的处理都是它的强项,所以这个behaviour非常之重要

1. 有限状态机

有限状态机可以用下面这个公式来表达

State(S) x Event(E) -> Actions(A), State(S')

表示的就是在S状态时如果有事件E发生,那么执行动作A后把状态调整到S’。理解很好理解,如果能够熟练应用必须得下苦功,多练习。

2. 一个例子

erlang手册中用这个例子来解释的:开锁问题,有一个密码锁的门,它就可以看作一个状态机,初始状态门是锁着的,任何时候有人按一个密码键就会产生一个事件,这个键值和前面的按键组合后与密码相比较,看是否正确,如果输入的密码顺序是对的,那么将门打开30秒,如果输入密码不完全,则等待下次按钮按下,如果输入密码顺序是错的,则重新开始等待按键按下。

  1. -module(code_lock).
  2. -behaviour(gen_fsm).
  3. -export([start_link/1]).
  4. -export([button/1]).
  5. -export([init/1, locked/2, open/2]).
  6. start_link(Code) ->
  7.     gen_fsm:start_link({local, code_lock}, code_lock, Code, []).
  8. button(Digit) ->
  9.     gen_fsm:send_event(code_lock, {button, Digit}).
  10. init(Code) ->
  11.     {ok, locked, {[], Code}}.
  12. locked({button, Digit}, {SoFar, Code}) ->
  13.     case [Digit|SoFar] of
  14.         Code ->
  15.             do_unlock(),
  16.             {next_state, open, {[], Code}, 3000};
  17.         Incomplete when length(Incomplete)<length(Code) ->
  18.             {next_state, locked, {Incomplete, Code}};
  19.         _Wrong ->
  20.             {next_state, locked, {[], Code}}
  21.     end.
  22. open(timeout, State) ->
  23.     do_lock(),
  24.     {next_state, locked, State}.

这些代码下面解释

3. 启动状态机

在上一节提到的例子里,我们使用code_lock:start_link(Code)启动gen_fsm

  1. start_link(Code) ->
  2.     gen_fsm:start_link({local, code_lock}, code_lock, Code, []).

start_link调用gen_fsm:start_link/4,启动一个新的gen_fsm进程并连接。
1)第一个参数{local, code_lock}指定名字,在本地注册为code_lock
2)第二个参数code_lock是回调模块
3)第三个参数Code是传递给回调模块init函数的参数,就是密码锁的密码
4)第四个[]是状态机的选项
如果进程注册成功,则新的gen_fsm进程调用code_lock:init(Code),返回{ok, StateName, StateData}。StateName是gen_fsm的初始状态,在这里返回的是locked,表示初始状态下门是锁着的,StateData是gen_fsm的内部状态,在这里Statedata是当前的按键顺序(初始时为空)和正确的锁代码,是个列表

  1. init(Code) ->
  2.     {ok, locked, {[], Code}}.

 

注意gen_fsm:start_link是同步的,直到gen_fsm进程初始化并准备好开始接受请求时才会返回。加入gen_fsm是监控树的一部分,那么gen_fsm:start_link必须被使用,也就是被一个监控者调用,gen_fsm:start则是启动单独的gen_fsm进程,也就是gen_fsm不是监控树的一部分

4. 事件通知

使用gen_fsm:send_event/2来实现按建事件的通知

  1. button(Digit) ->
  2.     gen_fsm:send_event(code_lock, {button, Digit}).


code_lock是gen_fsm的名字,且必须用这个名字启动进程,{button, Digit}是发送的事件,事件是作为消息发送给gen_fsm的,当事件被接收到,gen_fsm就调用StateName(Event, StateData),它的返回值应该是{next_state, StateName1, StateData1}。StateName是当前状态名称,而StateName1是将转换到的下一状态名称,StateData1是StateData的新值

  1. locked({button, Digit}, {SoFar, Code}) ->
  2.     case [Digit|SoFar] of
  3.         Code ->
  4.             do_unlock(),
  5.             {next_state, open, {[], Code}, 30000};
  6.         Incomplete when length(Incomplete)<length(Code) ->
  7.             {next_state, locked, {Incomplete, Code}};
  8.         _Wrong ->
  9.             {next_state, locked, {[], Code}};
  10.     end.
  11. open(timeout, State) ->
  12.     do_lock(),
  13.     {next_state, locked, State}.

假如门是锁着的且按了一个按键,完整的按键序列和密码相比较,根据比较结果来决定门是打开(状态切到open)还是保持locked状态。

5 超时

假如输入的密码正确,门被打开,locked/2函数返回下面的序列

  1. {next_state, open, {[], Code}, 30000};

30000表示超时30000毫秒,在30秒后,超时发生,调用StateName(timeout, StateData) ,门又重新锁上

  1. open(timeout, State) ->
  2.     do_lock(),
  3.     {next_state, locked, State}.

 

6. 所有状态事件

有时候一个事件可以到达gen_fsm进程的任何状态,取代用gen_fsm:send_event/2发送消息和写一段每个状态函数处理事件的代码,这个消息我们可以用gen_fsm:send_all_state_event/2 发送,用Module:handle_event/3处理

  1. -module(code_lock).
  2. ...
  3. -export([stop/0]).
  4. ...
  5. stop() ->
  6.     gen_fsm:send_all_state_event(code_lock, stop).
  7. ...
  8. handle_event(stop, _StateName, StateData) ->
  9.     {stop, normal, StateData}.

 

7. 停止

假如gen_fsm是监控树的一部分,则不需要停止方法,gen_fsm会自动被监控者停止。如果需要在结束前清理数据,那么shutdown strategy必须为一个timeout,并且必须在gen_fsm的init方法里设置捕获exit信号,然后gen_fsm进程会调用callback方法terminate(shutdown, StateName, StateData)

  1. init(Args) ->
  2.     ...,
  3.     process_flag(trap_exit, true),
  4.     ...,
  5.     {ok, StateName, StateData}.
  6. ...
  7. terminate(shutdown, StateName, StateData) ->
  8.     ..code for cleaning up here..
  9.     ok.

 

8. 独立gen_fsm进程

加入gen_fsm不是监控树的一部分,stop函数可能有用,如:

  1. ...
  2. -export([stop/0]).
  3. ...
  4. stop() ->
  5.     gen_fsm:send_all_state_event(code_lock, stop).
  6. ...
  7. handle_event(stop, _StateName, StateData) ->
  8.     {stop, normal, StateData}.
  9. ...
  10. terminate(normal, _StateName, _StateData) ->
  11.     ok.


 回调函数处理stop事件并返回{stop, normal, StateData1},normal表示正常停止,StateData1为gen_fsm的新的StateData值,这将导致gen_fsm调用terminate(normal, StateName, StateData1)然后自然的停止

 9. 处理其他信息

收到的其他消息由handle_info(Info, StateName, StateData)处理,其他消息的一个例子就是exit消息,假如gen_fsm进程与其他进程link了并且trace了信号,就要处理exit消息

  1. handle_info({'EXIT', Pid, Reason}, StateName, StateData) ->
  2.     ..code to handle exits here..
  3.     {next_state, StateName1, StateData1}.


 补充:gen_fsm exports and callbacks 

gen_fsm module                          Callback module 
gen_fsm:start_link -------------------> Module:init/1 
gen_fsm:start 
gen_fsm:send_event -------------------> Module:StateName/2 
gen_fsm:send_all_state_event ---------> Module:handle_event/3 
gen_fsm:sync_send_event --------------> Module:StateName/3 
gen_fsm:sync_send_all_state_event ----> Module:handle_sync_event/4 
gen_fsm:reply 
gen_fsm:send_event_after 
gen_fsm:start_timer 
gen_fsm:cancel_timer 
gen_fsm:enter_loop 
                                        Module:handle_info/3 
                                        Module:terminate/3 
                                        Module:code_change/4

转载自:

阅读(1226) | 评论(0) | 转发(0) |
给主人留下些什么吧!~~