好几位同学一直不用状态机,什么都是gen_sever,有的说是不熟悉,有的说是有必要用吗?其实erlang的状态机gen_fsm也是由gen_server实现的,这点看gen_fsm.erl就很清楚了。关键是我们要在什么情况下来使用状态机呢,根据个人经验:如果一件事情有很明显的多个状态(阶段),那么使用状态机。比如:电商那边的淡季,旺季,遇到各种日子的打折促销活动;游戏里面的战斗,副本,活动等等,都推荐使用状态机。会给我们带来很多便利,减少程序的逻辑复杂程度,提高可读性。
先给出gen_fsm的基本行为行为和对应的回调函数,最好在心里有个基本概念:erlang的gen_fsm是一个基于事件驱动的有限状态机。理解了这个基本概念后,就便于理解相关的程序了。
- gen_fsm module Callback module
- -------------- ---------------
- gen_fsm:start_link -----> Module:init/1
- 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
- - -----> Module:handle_info/3
- - -----> Module:terminate/3
- - -----> Module:code_change/4
来看一个实际的例子。一个机器可以安排任务,根据任务数量的多少分为busy(忙碌)和idel(空闲)状态,只有在空闲状态下,才允许给机器增加新的任务;只有在忙碌状态下,才允许给机器删减任务。就这样很简单的一个应用,我们用状态机来做:
- %%----------------------------------------------------
- %% @doc 状态机演示
- %% <div>处于繁忙状态时,不可以再向其增加新任务;处于空闲状态时,不可以删减其任务。<div>
- %% @author [email protected]
- %%----------------------------------------------------
- -module(demo).
- -behaviour(gen_fsm).
- %% 外部api
- -export(
- [
- start_link/0
- ,stop/0
- ,add_task/1
- ,delete_task/1
- ]
- ).
- %% fsm回调函数
- -export([init/1, handle_event/3, handle_sync_event/4, handle_info/3, terminate/3, code_change/4]).
- -define(LIMIT, 2).
- -record(state, {
- task = [] %% [{TaskName, Heavy} | T]
- }).
- %%======================================
- %% 外部api
- %%======================================
- %% @spec start_link() -> {ok, Pid::pid()}
- %% @doc 启动状态机
- start_link()->
- gen_fsm:start_link({local, ?MODULE}, ?MODULE, [], []).
- stop() ->
- gen_fsm:send_all_state_event(?MODULE, stop).
- %% @spec add_task(TaskName) -> {ok, NewTask} | {error, Reason}
- %% @type TaskName = atom(), NewTask = term()
- %% @doc 增加一个任务--只能在空闲状态下增加任务
- add_task({TaskName, Heavy}) ->
- gen_fsm:sync_send_all_state_event(?MODULE, {add_task, TaskName, Heavy}).
- %% @spec delete_task(TaskName) -> {ok, NewTask} | {error, Reason}
- %% @type TaskName = atom(), NewTask = term()
- %% @doc 删除一个任务--只能在繁忙状态下删减任务
- delete_task(TaskName) ->
- gen_fsm:sync_send_all_state_event(?MODULE, {delete_task, TaskName}).
- %%=======================================
- %% FSM回调函数
- %%=======================================
- init([])->
- {ok, idel, #state{}}.
- handle_event(stop, _StateName, State) ->
- {stop, normal, State};
- handle_event(_Event, StateName, State) ->
- {next_state, StateName, State}.
- %% 增加一个任务
- handle_sync_event({add_task, TaskName, Heavy}, _From, idel, State = #state{task = Task}) ->
- NewTask = case lists:keyfind(TaskName, 1, Task) of
- {TaskName, _} ->
- Task1 = lists:keydelete(TaskName, 1, Task),
- [{TaskName, Heavy} | Task1];
- false ->
- [{TaskName, Heavy} | Task]
- end,
- StateName = case length(NewTask) >= ?LIMIT of
- true -> busy;
- false -> idel
- end,
- {reply, {ok, NewTask}, StateName, State#state{task = NewTask}};
- handle_sync_event({add_task, _TaskName, _Heavy}, _From, StateName, State) ->
- {reply, {error, too_busy}, StateName, State};
- %% 删减一个任务
- handle_sync_event({delete_task, TaskName}, _From, busy, State = #state{task = Task}) ->
- NewTask = lists:keydelete(TaskName, 1, Task),
- StateName = case length(NewTask) < ?LIMIT of
- true -> idel;
- false -> busy
- end,
- {reply, {ok, NewTask}, StateName, State#state{task = NewTask}};
- handle_sync_event({delete_task, _TaskName}, _From, StateName, State) ->
- {reply, {error, too_idel}, StateName, State};
- handle_sync_event(_Event, _From, StateName, State) ->
- Reply = ok,
- {reply, Reply, StateName, State}.
- handle_info(_Info, StateName, State) ->
- {next_state, StateName, State}.
- terminate(_Reason, _StateName, _State) ->
- ok.
- code_change(_OldVsn, StateName, State, _Extra) ->
- {ok, StateName, State}.
感兴趣的同学可以自己去编译运行一次。这里我们运用了不同状态处理不同的事件。如果觉得不满足的话,接下来我们在最后加入状态函数,这里只是简单的演示,实际的应用都可根据这种方法去扩展和改变,下面是完整的代码:
- %%----------------------------------------------------
- %% @doc 状态机演示
- %% <div>处于繁忙状态时,不可以再向其增加新任务;处于空闲状态时,不可以删减其任务。<div>
- %% @author [email protected]
- %%----------------------------------------------------
- -module(demo).
- -behaviour(gen_fsm).
- %% 外部api
- -export(
- [
- start_link/0
- ,stop/0
- ,add_task/1
- ,delete_task/1
- ,info/0
- ]
- ).
- %% 状态函数
- -export([
- idel/3
- ,busy/3
- ]).
- %% fsm回调函数
- -export([init/1, handle_event/3, handle_sync_event/4, handle_info/3, terminate/3, code_change/4]).
- -define(LIMIT, 2).
- -record(state, {
- task = [] %% [{TaskName, Heavy} | T]
- }).
- %%======================================
- %% 外部api
- %%======================================
- %% @spec start_link() -> {ok, Pid::pid()}
- %% @doc 启动状态机
- start_link()->
- gen_fsm:start_link({local, ?MODULE}, ?MODULE, [], []).
- stop() ->
- gen_fsm:send_all_state_event(?MODULE, stop).
- %% @spec add_task(TaskName) -> {ok, NewTask} | {error, Reason}
- %% @type TaskName = atom(), NewTask = term()
- %% @doc 增加一个任务--只能在空闲状态下增加任务
- add_task({TaskName, Heavy}) ->
- gen_fsm:sync_send_all_state_event(?MODULE, {add_task, TaskName, Heavy}).
- %% @spec delete_task(TaskName) -> {ok, NewTask} | {error, Reason}
- %% @type TaskName = atom(), NewTask = term()
- %% @doc 删除一个任务--只能在繁忙状态下删减任务
- delete_task(TaskName) ->
- gen_fsm:sync_send_all_state_event(?MODULE, {delete_task, TaskName}).
- %% @spec info() -> {ok, Info}
- %% @doc 查看当前状态
- info() ->
- gen_fsm:sync_send_event(?MODULE, info).
- %%=======================================
- %% FSM回调函数
- %%=======================================
- init([])->
- {ok, idel, #state{}}.
- handle_event(stop, _StateName, State) ->
- {stop, normal, State};
- handle_event(_Event, StateName, State) ->
- {next_state, StateName, State}.
- %% 增加一个任务
- handle_sync_event({add_task, TaskName, Heavy}, _From, idel, State = #state{task = Task}) ->
- NewTask = case lists:keyfind(TaskName, 1, Task) of
- {TaskName, _} ->
- Task1 = lists:keydelete(TaskName, 1, Task),
- [{TaskName, Heavy} | Task1];
- false ->
- [{TaskName, Heavy} | Task]
- end,
- StateName = case length(NewTask) >= ?LIMIT of
- true -> busy;
- false -> idel
- end,
- {reply, {ok, NewTask}, StateName, State#state{task = NewTask}};
- handle_sync_event({add_task, _TaskName, _Heavy}, _From, StateName, State) ->
- {reply, {error, too_busy}, StateName, State};
- %% 删减一个任务
- handle_sync_event({delete_task, TaskName}, _From, busy, State = #state{task = Task}) ->
- NewTask = lists:keydelete(TaskName, 1, Task),
- StateName = case length(NewTask) < ?LIMIT of
- true -> idel;
- false -> busy
- end,
- {reply, {ok, NewTask}, StateName, State#state{task = NewTask}};
- handle_sync_event({delete_task, _TaskName}, _From, StateName, State) ->
- {reply, {error, too_idel}, StateName, State};
- handle_sync_event(_Event, _From, StateName, State) ->
- Reply = ok,
- {reply, Reply, StateName, State}.
- handle_info(_Info, StateName, State) ->
- {next_state, StateName, State}.
- terminate(_Reason, _StateName, _State) ->
- ok.
- code_change(_OldVsn, StateName, State, _Extra) ->
- {ok, StateName, State}.
- %%===================================
- %% 状态函数
- %%===================================
- idel(info, _From, State) ->
- {reply, {ok, idel}, idel, State}.
- busy(info, _From, State) ->
- {reply, {ok, busy}, busy, State}.
最后需要记得,状态的划分太多和太少都不利于程序的编写和阅读。