erlang四大behaviour之二-gen_fsm(轉載)

今天介紹erlang的一個非常重要的behaviour,就是gen_fsm-有限狀態機,有限狀態機的作用非常之多,比如文本解析,模式匹配、遊戲邏輯等等方面的處理都是它的強項,所以這個behaviour非常之重要

1. 有限狀態機

有限狀態機可以用下面這個公式來表達

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

表示的就是在S狀態時如果有事件E發生,那麼執行動作A後把狀態調整到S’。理解很好理解,如果能夠熟練應用必須得下苦功,多練習。

2. 一個例子

erlang手冊中用這個例子來解釋的:開鎖問題,有一個密碼鎖的門,它就可以看作一個狀態機,初始狀態門是鎖着的,任何時候有人按一個密碼鍵就會產生一個事件,這個鍵值和前面的按鍵組合後與密碼相比較,看是否正確,如果輸入的密碼順序是對的,那麼將門打開30秒,如果輸入密碼不完全,則等待下次按鈕按下,如果輸入密碼順序是錯的,則重新開始等待按鍵按下。

-module(code_lock).-behaviour(gen_fsm).-export([start_link/1]).-export([button/1]).-export([init/1, locked/2, open/2]).start_link(Code) ->    gen_fsm:start_link({local, code_lock}, code_lock, Code, []).button(Digit) ->    gen_fsm:send_event(code_lock, {button, Digit}).init(Code) ->    {ok, locked, {[], Code}}.locked({button, Digit}, {SoFar, Code}) ->    case [Digit|SoFar] of        Code ->            do_unlock(),            {next_state, open, {[], Code}, 3000};        Incomplete when length(Incomplete)<length(Code) ->            {next_state, locked, {Incomplete, Code}};        _Wrong ->            {next_state, locked, {[], Code}}    end.open(timeout, State) ->    do_lock(),    {next_state, locked, State}.

這些代碼下面解釋

 

3. 啓動狀態機

在上一節提到的例子裏,我們使用code_lock:start_link(Code)啓動gen_fsm

start_link(Code) ->    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是當前的按鍵順序(初始時爲空)和正確的鎖代碼,是個列表

init(Code) ->    {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來實現按建事件的通知

button(Digit) ->    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的新值

locked({button, Digit}, {SoFar, Code}) ->    case [Digit|SoFar] of        Code ->            do_unlock(),            {next_state, open, {[], Code}, 30000};        Incomplete when length(Incomplete)<length(Code) ->            {next_state, locked, {Incomplete, Code}};        _Wrong ->            {next_state, locked, {[], Code}};    end.open(timeout, State) ->    do_lock(),    {next_state, locked, State}.

假如門是鎖着的且按了一個按鍵,完整的按鍵序列和密碼相比較,根據比較結果來決定門是打開(狀態切到open)還是保持locked狀態。

5 超時

假如輸入的密碼正確,門被打開,locked/2函數返回下面的序列

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

30000表示超時30000毫秒,在30秒後,超時發生,調用StateName(timeout, StateData) ,門又重新鎖上

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

 

6. 所有狀態事件

有時候一個事件可以到達gen_fsm進程的任何狀態,取代用gen_fsm:send_event/2發送消息和寫一段每個狀態函數處理事件的代碼,這個消息我們可以用gen_fsm:send_all_state_event/2 發送,用Module:handle_event/3處理

-module(code_lock)....-export([stop/0])....stop() ->    gen_fsm:send_all_state_event(code_lock, stop)....handle_event(stop, _StateName, StateData) ->    {stop, normal, StateData}.

 

7. 停止

假如gen_fsm是監控樹的一部分,則不需要停止方法,gen_fsm會自動被監控者停止。如果需要在結束前清理數據,那麼shutdown strategy必須爲一個timeout,並且必須在gen_fsm的init方法裏設置捕獲exit信號,然後
gen_fsm進程會調用callback方法terminate(shutdown, StateName, StateData)

init(Args) ->    ...,    process_flag(trap_exit, true),    ...,    {ok, StateName, StateData}....terminate(shutdown, StateName, StateData) ->    ..code for cleaning up here..    ok.

 

8. 獨立gen_fsm進程

加入gen_fsm不是監控樹的一部分,stop函數可能有用,如:

...-export([stop/0])....stop() ->    gen_fsm:send_all_state_event(code_lock, stop)....handle_event(stop, _StateName, StateData) ->    {stop, normal, StateData}....terminate(normal, _StateName, _StateData) ->    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消息

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

本文屬轉載

原作者:老紀
原載:
老紀博客版權所有。轉載時必須以鏈接形式註明轉載自老紀博客 [http://www.jifuyi.com/]。
本文鏈接地址:
http://www.jifuyi.com/one-of-erlang-behaviour-gen-fsm/

http://www.cnblogs.com/puputu/articles/1701012.html

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章