erlang四大行为模式

erlang四大行为模式

文章有转载复制其他博客内容,如有侵权,请联系删除

首先来简单说下 行为模式, **行为模式(behaviour)**是面向进程编程中各种常见模式的一种形似化表述.

OTP行为模式 将 一些反复出现的模式分成了两部分: 通用部分具体应用相关的实现部分, 二者通过一套简单明确的接口进行通信.

  1. 行为模式的组成部分:
  • 行为模式接口: 是一组特定的函数和相关调用规范. gen_server行为模式接口: init/ , handle_call/3 , handle_cast/2 , handle_info/2 , terminate/2, code_change/3
  • 行为模式实现: 是程序员提供的具体应用相关的代码, 导出了接口所需的全部函数的回调函数.
  • 行为模式容器: 就是一个进程, 会调用与行为模式实现相对应的回调模块处理应用相关的逻辑.
  1. 行为模式实例化: 启动一个新容器进程.
  2. OTP行为模式的好处:
  • 可以大大缩减代码量, 开发者可以用更少的代码完成更多的事情
  • 代码稳定可靠, 坚如磐石, 核心库代码经过了严酷的测试
  • 代码可以被嵌入更发, 提供更强劲功能的OTP框架, 如监督树
  • 在了解某种模式后, 相同行为模式的实现代码更容易理解

erlang四大行为模式:

  • gen_server: 通用服务器
  • gen_event: 通用事件处理
  • gen_fsm: 有限状态机/ gen_statem: 泛型状态机
  • supervisor: 通用监督者

gen_server 通用服务器

gen_server 实现C/S模型, 把behaviour 和 回调模块需要完成的职责分离开, 用于多个客户共用一个资源的情况.

1. gen_event提供的主要的库函数 及 回调函数
gen_server module            Callback module						Desc
-----------------            ---------------						----------------
gen_server:start
gen_server:start_link -----> Module:init/1							启动gen_server

gen_server:stop       -----> Module:terminate/2						终止gen_server

gen_server:call
gen_server:multi_call -----> Module:handle_call/3					gen_server同步调用, 

gen_server:cast
gen_server:abcast     -----> Module:handle_cast/2					gen_server异步调用

-                     -----> Module:handle_info/2					gen_server进程默认方式接收到消息的处理

-                     -----> Module:terminate/2

-                     -----> Module:code_change/3
  • 启动服务器
%% 启动函数 gen_server:start/3, gen_server:start/4, gen_server:start_link/3, gen_server:start_link/4, gen_server:start_monitor/3, gen_server:start_monitor/4
%% gen_server:start_link/3, gen_server:start_link/4启动函数创建的gen_server作为监督树的一部分, 链接到监督者
gen_server:start(Module :: module(), Args :: term(), Options :: [start_opt()] ) -> start_ret().
gen_server:start(ServerName :: server_name(), Module :: module(), Args :: term(), Options :: [start_opt()] ) -> start_ret().
gen_server:start_link(Module :: module(), Args :: term(), Options :: [start_opt()] ) -> start_ret().
gen_server:start_link(ServerName :: server_name(), Module :: module(), Args :: term(), Options :: [start_opt()] ) -> start_ret().		

%% 回调函数
Module:init(Args :: term()) ->  %% 初始化进程状态
    {ok, State} | 				%% State 为初始化进程状态, 一般为记录格式, 需要在回调模块头部声明
    {ok,State,Timeout} | 		%% 增加一个超时时间(ms), 在超时后调用handle_info(timeout, State)
    {ok,State,hibernate} | 		%% 服务器初始化完成后就进入休眠
    {ok,State,{continue,Continue}} | 	%% 立即调用handle_continue(Continue, State),
    {stop,Reason}.  

gen_server:start/4 , gen_server:start_link/4 使用参数ServerName 创建具有注册名的gen_server.

ServerName指定了进程的名字,格式可以是{local, Name},{global, Name},可不指定;
Module指定了gen_sever进程的回调模块;
如果ServerName{local, Name},会通过register/2将进程注册为Name。为{global, Name}则通过global:register_name/2将进程注册为Name
Args会作为参数传递给Module:init(Args);

启动服务器的回调函数是: Module:init/1 , 参数为启动函数的传入的 Args:: term().

gen_server的 启动过程是同步的, 启动函数在 Module:init/1 返回之前不会返回.

  • 向服务器进程发消息(同步发消息需要获取应答)
%% 同步发送消息
gen_server:call(ServerRef :: server_ref(), Request :: term()) -> Reply :: term().
gen_server:call(ServerRef :: server_ref(), Request :: term(), TimeOut :: timeout()) -> Reply :: term().
%% 回调函数
Module:handle_call(Request :: term(), From :: from(), State) -> 
	{reply,Reply,NewState}   		% Reply被发送回客户端请求, 作为返回值返回; NewState 为gen_server新的内部状态
	| {reply,Reply,NewState,Timeout}		% 超时后会有Module:handle_info/2来处理, 一般gen_server进入休眠
	| {reply,Reply,NewState,hibernate}		% 进入休眠状态, 等待下一条消息
	| {reply,Reply,NewState,{continue,Continue}}	%执行Module:handle_continue/2回调函数, Continue为第一个参数
	| {noreply,NewState}					% 不回复
	| {noreply,NewState,Timeout}
	| {noreply,NewState,hibernate}
	| {noreply,NewState,{continue,Continue}}
	| {stop,Reason,Reply,NewState}
	| {stop,Reason,NewState}.

%% 异步发送消息
gen_server:cast(ServerRef :: server_ref(), Request :: term()) -> Reply :: term().
gen_server:cast(ServerRef :: server_ref(), Request :: term(), TimeOut :: timeout()) -> Reply :: term().
%% 回调函数
Module:handle_cast(Request :: term(), State)
    {noreply,NewState}			% 无返回;  NewState 为gen_server新的内部状态
	| {noreply,NewState,Timeout}		% 超时后会有Module:handle_info/2来处理, 一般gen_server进入休眠
	| {noreply,NewState,hibernate}		% 进入休眠状态, 等待下一条消息
	| {noreply,NewState,{continue,Continue}}
	| {stop,Reason,NewState}
  • Module:handle_call/3: 同步请求回调. 每次收到由gen_server:call/2发送的消息时这个函数就会被调用. 接收3个参数: 消息, From(发消息的进程), 服务器状态State.

  • Module:handle_cast/2: 异步请求回调. 每次收到由gen_server:cast/2发送异步消息时这个函数就会被调用. 发送完毕后无需等待任何应答.

  • Module:handle_info/2: 未经gen_server:call/2gen_server:cast/2带外消息 处理. 依赖于直接消息通信(如套接字 或 端口驱动) 时, handle_info会被调用处理. 在回调函数返回带有Timeout 超时时间时, handle_info 会处理超时信息 ; 搭配服务器进程中调用erlang:start_timer(Timeout, self(), Msg) , handle_info接收超时信息并处理可实现服务器进程的定时器;

...
erlang:start_timer(Timeout, self(), Msg),
...

handle_info({timeout, _Ref, Msg}, State) ->
    ... %% 超时处理
	{noreply, NState};
  • Module:handle_continue/2 : 仅当另一个回调返回包含{continue, Continue} , handle_continue/2会被立即调用. 用于将某个功能分布执行. 返回结果与handle_cast/2类似

  • Module:code_change/3: 用于在版本升级/降级时更新服务器内部状态.

  • Module:terminate/2: 服务器执行gen_server:stop/2, gen_server:stop/3时会被调用, 服务器进程正常结束, 返回前可以做一些必要的清理, 会返回Reason终结服务器进程.

2. 使用
-module(tcp_server).

%% API
-export([
    start_link/0
]).
-export([init/1, handle_call/3, handle_cast/2, handle_info/2, terminate/2, code_change/3]).

-include("common.hrl").

-record(state, {num = 0, socket}).

-define(port, 5677).
-define(host, {192,168,0,0}). %% IP

start_link() ->
    gen_server:start_link({local, ?MODULE}, ?MODULE, [], []).

%% ---------------------------------------------------
%% 回调函数
%% ---------------------------------------------------
init([]) ->
    {ok, Listen} = gen_tcp:listen(?port, [binary, {packet, 0}, {reuseaddr, true}, {active, true}]),
    accept(Listen),
%%    {ok, _Socket} = gen_tcp:accept(Listen),
%%    gen_tcp:close(Listen),
    State = #state{socket = Listen},
    {ok, State}.
    
handle_call(_Request, _From, State) ->
    {noreply, State}.
    
handle_cast(_Msg, State) ->
    {noreply, State}.
    
handle_info({'EXIT', _, normal}, State) ->
    {noreply, State};
handle_info(_Info, State) ->
    {noreply, State}.

terminate(_Reason, _State) ->
    ok.

code_change(_OldVsn, State, _Extra) ->
    {ok, State}.

accept(Listen) ->
    case gen_tcp:accept(Listen) of
        {ok, Socket} ->
            case tcp_role:start_link(Socket) of
                {ok, Pid} ->
                    ?INFO("============== ~w", [Pid]),
                    case gen_tcp:controlling_process(Socket, Pid) of
                        ok -> ok;
                        _ -> ok
                    end;
                _ ->
                    ?INFO("转换失败"),
                    ok
            end;
        _ ->
            ?INFO("监听失败 ===================="),
            ok
    end,
    accept(Listen).

gen_event 通用事件管理器

按照书上定义在OTP中,它由通用事件管理器进程组成,该进程 可 动态添加和删除的任意数量的事件处理程序。事件可以是例如错误,警报或要记录的某些信息

简单来说,就是gen_event行为运行一个了一个事件管理进程,该进程接受消息(事件),并根据消息(事件)做对应的事件处理,而提供的对应事件处理其实就是添加的“回调函数”(事件处理器). 与gen_server不一样的是,gen_event把回调的函数分类成多个module并称为事件处理器.

一个事件管理器可以安装0,1,N个事件处理器,当一个事件管理器接受到一个事件的通知时,这个事件将会被所有的已安装的事件处理器处理(如图)。

事件处理器处理

1. gen_event提供的主要的库函数 及 回调函数
gen_event函数库                     回调函数							描述
----------------                   ---------------					-----------------
gen_event:start														
gen_event:start_monitor
gen_event:start_link       ----->  -								启动gen_event事件管理器

gen_event:add_handler												增加gen_event事件处理器
gen_event:add_sup_handler  ----->  Module:init/1					增加gen_event事件处理器, 通过链接来监督连接和调用过程

gen_event:notify													异步触发事件, 会到所有事件处理器中匹配
gen_event:sync_notify      ----->  Module:handle_event/2			同步触发事件

gen_event:send_request
gen_event:call             ----->  Module:handle_call/2

-                          ----->  Module:handle_info/2

gen_event:delete_handler   ----->  Module:terminate/2				删除事件处理器

gen_event:swap_handler												替换事件处理器
gen_event:swap_sup_handler ----->  Module1:terminate/2				替换事件处理器, 通过链接来监督连接和调用过程
                                   Module2:init/1

gen_event:which_handlers   ----->  -								返回事件管理器中所有事件处理器的列表

gen_event:stop             ----->  Module:terminate/2

-                          ----->  Module:code_change/3
2. 如何使用
  • 创建事件管理器
  • 向管理器增加事件处理器
  • 编写事件处理器程序
  • 通知事件管理器

以《erlang趣学指南》的冰球比赛为案例

-module(curling).
-export([start_link/2, set_teams/3, add_points/3, next_round/1]).
-export([join_feed/2, leave_feed/2]).
-export([game_info/1]).
 
start_link(TeamA, TeamB) ->
    %%创建事件管理进程
    {ok, Pid} = gen_event:start_link(),
    %% 添加事件处理器 gen_event:add_handler(事件管理进程pid, 回调模块, 参数),curling_scoreboard为计分板
    gen_event:add_handler(Pid, curling_scoreboard, []),
    %% accumulator为累加器
    gen_event:add_handler(Pid, curling_accumulator, []),
    set_teams(Pid, TeamA, TeamB),
    {ok, Pid}.
 
%%设置队伍
set_teams(Pid, TeamA, TeamB) ->
    %%事件通知
    gen_event:notify(Pid, {set_teams, TeamA, TeamB}).
%%添加分数
add_points(Pid, Team, N) ->
    gen_event:notify(Pid, {add_points, Team, N}).
%%下一回合
next_round(Pid) ->
    gen_event:notify(Pid, next_round).
 
%% Subscribes the pid ToPid to the event feed.
%% The specific event handler for the newsfeed is
%% returned in case someone wants to leave
%%join_feed(Pid, ToPid) ->
%%    HandlerId = {curling_feed, make_ref()},
%%    gen_event:add_sup_handler(Pid, HandlerId, [ToPid]),
%%    HandlerId.
%%
%%leave_feed(Pid, HandlerId) ->
%%    gen_event:delete_handler(Pid, HandlerId, leave_feed).
 
%% Returns the current game state.
game_info(Pid) ->
    gen_event:call(Pid, curling_accumulator, game_data).

事件处理器

-module(curling_scoreboard).
-behaviour(gen_event).
 
-export([init/1, handle_event/2, handle_call/2, handle_info/2, code_change/3,
         terminate/2]).
 
%%计分板
init([]) ->
    io:format("curling_scoreboard init ~n"),
    {ok, []}.
 
handle_event({set_teams, TeamA, TeamB}, State) ->
    io:format("curling_scoreboard set_teams ~n"),
    io:format("Scoreboard: Team ~s vs. Team ~s~n", [TeamA, TeamB]),
%%    curling_scoreboard_hw:set_teams(TeamA, TeamB),
    {ok, State};
handle_event({add_points, Team, N}, State) ->
    io:format("curling_scoreboard add_points ~n"),
    [io:format("Scoreboard: increased score of team ~s by 1~n", [Team]) || _ <- lists:seq(1,N)],
%%    [curling_scoreboard_hw:add_point(Team) || _ <- lists:seq(1,N)],
    {ok, State};
handle_event(next_round, State) ->
    io:format("curling_scoreboard next_round ~n"),
    io:format("Scoreboard: round over~n"),
%%    curling_scoreboard_hw:next_round(),
    {ok, State};
handle_event(_, State) ->
    {ok, State}.
 
handle_call(_, State) ->
    {ok, ok, State}.
 
handle_info(_, State) ->
    {ok, State}.
 
code_change(_OldVsn, State, _Extra) ->
    {ok, State}.
 
terminate(_Reason, _State) ->
    ok.
-module(curling_accumulator).
-behaviour(gen_event).
 
-export([init/1, handle_event/2, handle_call/2, handle_info/2, code_change/3,
         terminate/2]).
 
-record(state, {teams=orddict:new(), round=0}).
 
%% 累加器
 
init([]) ->
    io:format("accumulator init ~n"),
    {ok, #state{}}.
 
handle_event({set_teams, TeamA, TeamB}, S=#state{teams=T}) ->
    io:format("accumulator set_teams ~n"),
    Teams = orddict:store(TeamA, 0, orddict:store(TeamB, 0, T)),
    {ok, S#state{teams=Teams}};
handle_event({add_points, Team, N}, S=#state{teams=T}) ->
    io:format("accumulator add_points ~n"),
    Teams = orddict:update_counter(Team, N, T),
    {ok, S#state{teams=Teams}};
handle_event(next_round, S=#state{}) ->
    io:format("accumulator next_round ~n"),
    {ok, S#state{round = S#state.round+1}};
handle_event(_Event, Pid) ->
    {ok, Pid}.
 
handle_call(game_data, S=#state{teams=T, round=R}) ->
    {ok, {orddict:to_list(T), {round, R}}, S};
handle_call(_, State) ->
    {ok, ok, State}.
 
handle_info(_, State) ->
    {ok, State}.
 
code_change(_OldVsn, State, _Extra) ->
    {ok, State}.
 
terminate(_Reason, _State) ->
    ok.

supervisor监督者

supervisor模块提供了一个监控其他进程的进程。通过supervisor能够构建监控树,用于构造容错应用程序。监控者能够启动、关闭及重启它的子进程。

监督者的启动

supervisor进程通过supervisor:start_link(Module, Args)supervisor:start_link(SupName, Module, Args)启动,成功的话返回值为{ok, Pid}。通过调用回调函数Module:init(Args)来初始化监supervisor进程,如果成功,init将返回{ok, {RestartStrategy, [ChildSpec]}}

  • RestartStrategy定义了supervisor进程的重启策略, 一般是元组{Strategy, Intensity, Period}

Strategy表示子进程的重启策略,主要有以下几种:
one_for_one:一个子进程挂掉后,只有该子进程会被重启。默认选项;
one_for_all:一个子进程挂掉后,其他子进程也会被终止,然后重启所有子进程;
rest_for_one:一个子进程挂掉后,在该子进程后启动的子进程会被终止,然后重启这些子进程;注意:所有子进程是按照列表顺序去启动的,终止时,按照列表顺序的逆序去终止。
simple_one_for_one:简单版的one_for_one,在这种策略下,所有子进程都是动态添加的。此时返回的ChildSpec只有一个元组.

IntensityPeriod共同指定supervisor的重启限度, 默认为15,表示5s内最多重启1次, 超过这个限度后,supervisor进程将会终止所有子进程和它本身。能够有效地防止supervisor无限重启。注意,Intensity=2Period=10Intensity=1Period=5是不同的,{1,5}短时间内不允许重启2次而{2,10}是允许的。

  • ChildSpec定义了如何启动和管理子进程, 一般是元组: {Id, Start, Restart, Shutdown, Type, Modules}
#{id => Id,       		  % mandatory
  start => {M,F,A}},      % mandatory
  restart => permanet|temporary|transient, % optional
  shutdown => brutal_kill|TimeOut, % optional
  type => supervisor|worker,       % optional
  modules => modules()}   % optional

Id为子进程的标识符;
Start是一个{M,F,A}元组,用于启动子进程;
Restart是指定了子进程终止时的处理策略,permanent参数表示每次终止都重启,temporary表示不再重启(即使因为其他进程终止导致本进程终止而后需要重启的情况),transient表示只有当进程被意外终止才会重启,正常原因包括normal, shutdown, or {shutdown,Term}; 默认为permanent
Shutdown指定子进程的终止方式,brutal_kill表示通过调用exit(ChildPid, kill)终止子进程,TimeOut表示表示通过调用exit(ChildPid, shutdown)终止子进程,此时将会等待TimeOut ms等待子进程返回一个理由为shutdown的退出信号。当子进程为supervisor进程时,应该设置成infinity。默认为50000;
Type指定子进程的类型,默认worker
Module指定回调模块,默认为{M,F,A}中的M

注意,在init返回后,supervisor会根据子进程列表[ChildSpec],启动所有的子进程。supervisor:start_link是同步调用,在所有子进程启动之前它不会返回。

supervisor函数库

supervisor:check_childspecs([ChildSpec])检查ChildSpec是否合法。
supervisor:count_children(SupRef)获取supervisor进程的子进程情况。
supervisor:start_child(SupRef, ChildSpec)动态启动一个子进程,不过该子进程在supervisor进程重启后将会丢失。
supervisor:terminate_child(SupRef, Id)终止一个子进程,无论该子进程是静态添加抑或是动态添加的。此处的Id,如果是动态添加的则必须为子进程Pid
supervisor:restart_child(SupRef, Id)重启一个子进程。此子进程必须在ChildSpecs中。
supervisor:delete_child(SupRef, Id)删除一个子进程,并从ChildSpecs中删除。

使用
%% one_for_one Supervisors
-module(ch_sup).
-behaviour(supervisor).

-export([start_link/0]).
-export([init/1]).

start_link() ->
    supervisor:start_link({local, ?MODULE}, ?MODULE, []).

init(_Args) ->
    SupFlags = {one_for_one,1,5},
    ChildSpecs = [#{id => ch3,
                    start => {ch3, start_link, []},
                    restart => permanent,
                    shutdown => brutal_kill,
                    type => worker,
                    modules => [ch3]}],
    {ok, {SupFlags, ChildSpecs}}.
%% Simplified one_for_one Supervisors
-module(simple_sup).
-behaviour(supervisor).

-export([start_link/0]).
-export([init/1]).

start_link() ->
    supervisor:start_link(local, ?MODULE}, ?MODULE, []).

init(_Args) ->
    SupFlags = {simple_one_for_one, 0,1},
    ChildSpecs = [#{id => ch4, start => {ch4, start_link, []}, restart => permanent, 
                    shutdown => brutal_kill, type => worker, modules => [ch4]}}],
    {ok, {SupFlags, ChildSpecs}}.

对于Simplified one_for_one Supervisors而言, 所有的子进程都是通过supervisor:start_child(Sup, [List])动态添加的,相当于调用apply(call, start_link, []++[List])call:start_link([List])。子进程通过supervisor:terminate_child(SupRef, Pid)终止。
需要注意的是,Simplified one_for_one Supervisors子进程的终止是异步的,终止顺序不可预测。

gen_statem通用状态机

gen_statemErlang/OTP 19.0 引入的新behavior。用于替换gen_fsm

gen_statem支持2种回调模式:

  • state_functions
    要求状态名StateName必须是原子,并且回调函数 的名称与状态 同名。这一点与gen_fsm 一样。

    Module:StateName(enter, OldState, Data) -> StateEnterResult(StateName)
    Module:StateName(EventType, EventContent, Data) -> StateFunctionResult
    
  • handle_event_function
    状态名可以是任何形式,此时回调函数为Module:handle_event/4 ,适用于所有状态下。

    Module:handle_event(enter, OldState, StateName, Data) -> StateEnterResult(StateName)
    Module:handle_event(EventType, EventContent, StateName, Data) -> HandleEventResult
    

**进入状态回调返回值: **

回调函数Module:callback_mode()指定gen_statem的回调模式。其返回值为state_functions|handle_event_function|[state_functions, state_enter]|[handle_event_function, state_enter]

callback_mode/0函数的返回列表中加入state_enter,会在每次状态改变的时候调用回调函数,参数为(enter, OldState, ...),即进入状态回调。

StateEnterResult(StateName) ->
    {next_state, StateName, NewData} |
    {next_state, StateName, NewData, Actions} |
    {keep_state, NewData} |
    {keep_state, NewData, Actions} |
    keep_state_and_data |
    {keep_state_and_data, Actions} |
    {repeat_state, NewData} |
    {repeat_state, NewData, Actions} |
    repeat_state_and_data |
    {repeat_state_and_data, Actions}    

其中,Actions表示状态迁移动作,在回调函数返回后指定gen_statem去执行,主要包含:

[postpone |
 {postpone, true | false} |
 {next_event, EventType :: event_type(), EventContent :: term()} |
 hibernate |
 {hibernate, true | false} |
 Timeout |
 {timeout, Time, EventContent} |
 {timeout, Time, EventContent, Options} |
 {{timeout, Name}, Time, EventContent} |
 {{timeout, Name}, Time, EventContent, Options} |
 {state_timeout, Time, EventContent} |
 {state_timeout, Time, EventContent, Options} |
 {reply, From, Reply}]

其中,

  • postpone表示在当前状态下延缓事件,等待状态改变时再重新触发;

  • hibernate表示挂起gen_statem进程直至下一个事件到来;

  • TimeOut{timeout, Time, EventContent}{timeout, Time, EventContent, Options}表示事件超时,一定时间内没有事件达到就会触发该超时。事件超时的定时器在有事件达到的时候就会被取消。超时后会触发事件timeout

  • {{timeout, Name}, Time, EventContent}{{timeout, Name}, Time, EventContent, Options}表示一般超时,一定时间后触发,与状态改变、事件更新无关。超时后会触发事件{timeout, Name}。可以通过设置一个同名的超时动作来重启这个定时器,如果想要终止这个定时器只需要将超时改成infinity即可;

  • {state_timeout, Time, EventContent}{state_timeout, Time, EventContent, Options}表示状态超时,一定时间内状态没有变化就会触发该超时。状态超时会在状态改变时被取消。超时后会触发事件state_timeout

  • {next_event, EventType, EventContent :: term()}表示像事件队列头部插入一个事件,该事件将会被优先处理,可用于触发所有事件。{next_event, internal, EventContent}触发的事件为internal,表示内部事件,用于区分外部事件;

    EventType事件类型包含:

    {call, From} | cast | info | timeout | {timeout, Name} | state_timeout | internal
    

    {call, From}castinfo这3种事件是通过接口产生的外部事件,其他的则是在执行过程中由gen_statem产生的。

    • castgen_statem:cast(ServerRef, Msg)生成,其中Msg会传递给参数EventContent
    • {call,From}gen_statem:call(ServerRef, Request)生成,其中Request会传递给参数EventContentFrom是通过迁移动作{reply,From,Reply}或在回调模块中调用gen_statem:reply(From, Reply)进行回复时的回复地址。
    • info由任何发送到gen_statem进程的普通消息生成。进程消息会传递给参数EventContent
    • state_timeout由迁移动作{state_timeout,Time,EventContent}状态定时器超时生成。
    • {timeout,Name}由迁移动作{{timeout,Name},Time,EventContent}一般定时器超时生成。
    • timeout由迁移动作{timeout,Time,EventContent}(或者它的缩写形式Time)事件定时器超时生成。
    • internal由迁移动作{next_event,internal,EventContent}生成。上面所有事件类型都可以通过使用next_event动作:{next_event,EventType,EventContent}生成。
  • {reply, From, Reply}用于给From返回值。除此之后也可以通过调用gen_statem:reply(From, Reply)进行返回。

需要注意的是,在Actions列表中的后面的Action是会覆盖前面的同类型的Action,例如,列表中的所有事件超时只有最后一个会生效。

1. behavior函数和回调函数之间的关系如下:
gen_statem module            Callback module												desc
-----------------            ---------------												----------------
gen_statem:start																			启动gen_statem状态机进程
gen_statem:start_link -----> Module:init/1													启动并链接状态机进程

Server start or code change
                      -----> Module:callback_mode/0											回调模式

gen_statem:stop       -----> Module:terminate/3

gen_statem:call
gen_statem:cast
erlang:send
erlang:'!'            -----> Module:StateName/3, Module:handle_event/3 <- (state_functions)						状态处理
                             Module:handle_event/4 <- (handle_event_function)				

-                     -----> Module:terminate/3

-                     -----> Module:code_change/4
2. gen_statem使用
  • 回调模式为state_function
-module(pushbutton). 
-behaviour(gen_statem). 
-export([start/0,push/0,get_count/0,stop/0]). 
-export([terminate/3,code_change/4,init/1,callback_mode/0]). 
-export([on/3,off/3]). 
name() -> pushbutton_statem. 
% The registered server name 
%% API. This example uses a registered name name() 
%% and does not link to the caller. 
start() -> 
    gen_statem:start({local,name()}, ?MODULE, [], []). 
push() -> 
    gen_statem:call(name(), push). 
get_count() -> 
    gen_statem:call(name(), get_count). 
stop() -> 
    gen_statem:stop(name()). 
%% Mandatory callback functions 
terminate(_Reason, _State, _Data) -> 
    void. 
code_change(_Vsn, State, Data, _Extra) -> 
    {ok,State,Data}. 
init([]) -> 
    %% Set the initial state + data. Data is used only as a counter.
    State = off, Data = 0, 
    {ok,State,Data}. 
callback_mode() -> 
    state_functions. 
%%% state callback(s) 
off({call,From}, push, Data) -> 
    %% Go to 'on', increment count and reply 
    %% that the resulting status is 'on' 
    {next_state,on,Data+1,[{reply,From,on}]}; 
off(EventType, EventContent, Data) -> 
    handle_event(EventType, EventContent, Data). 
on({call,From}, push, Data) -> 
    %% Go to 'off' and reply that the resulting status is 'off' 
    {next_state,off,Data,[{reply,From,off}]}; 
on(EventType, EventContent, Data) -> 
    handle_event(EventType, EventContent, Data). 
%% Handle events common to all states 
handle_event({call,From}, get_count, Data) -> 
    %% Reply with the current count 
    {keep_state,Data,[{reply,From,Data}]}; 
handle_event(_, _, Data) -> 
    %% Ignore all other events 
    {keep_state,Data}.
  • 回调模式为handle_event_function
-module(pushbutton). 
-behaviour(gen_statem). 
-export([start/0,push/0,get_count/0,stop/0]). 
-export([terminate/3,code_change/4,init/1,callback_mode/0]). 
-export([on/3,off/3]). 
name() -> pushbutton_statem. 
% The registered server name 
%% API. This example uses a registered name name() 
%% and does not link to the caller. 
start() -> 
    gen_statem:start({local,name()}, ?MODULE, [], []). 
push() -> 
    gen_statem:call(name(), push). 
get_count() -> 
    gen_statem:call(name(), get_count). 
stop() -> 
    gen_statem:stop(name()). 
%% Mandatory callback functions 
terminate(_Reason, _State, _Data) -> 
    void. 
code_change(_Vsn, State, Data, _Extra) -> 
    {ok,State,Data}. 
init([]) -> 
    %% Set the initial state + data. Data is used only as a counter.
    State = off, Data = 0, 
    {ok,State,Data}. 
callback_mode() -> 
    handle_event_function. 
%%% state callback(s) 
handle_event({call,From}, push, off, Data) -> 
    %% Go to 'on', increment count and reply 
    %% that the resulting status is 'on' 
    {next_state,on,Data+1,[{reply,From,on}]}; 
handle_event({call,From}, push, on, Data) -> 
    %% Go to 'off' and reply that the resulting status is 'off' 
    {next_state,off,Data,[{reply,From,off}]}; 
%% %% Event handling common to all states 
handle_event({call,From}, get_count, State, Data) -> 
    %% Reply with the current count 
    {next_state,State,Data,[{reply,From,Data}]}; 
handle_event(_, _, State, Data) -> 
    %% Ignore all other events 
    {next_state,State,Data}.
  • 11
    点赞
  • 9
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值