Erlang:gen_fsm与gen_statem

简介

从Erlang / OTP 19开始,gen_fsm被gen_statem继承。即使在OTP中,ssl等也已转移到gen_statem。

状态机

在服务器等程序中,状态管理很重要。根据当前状态(S)和生成的事件(E)的组合,将该状态改变建模为要执行的处理(A)和下一状态(S’),如下例子。

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

这通常使用gen_fsm行为来实现,其中state(S)是函数名称,事件(E)由模式匹配显式表示。但gen_statem是gen_fsm的增强。

gen_fsm到gen_statem

首先,gen_statem组织行为回调API, 它与gen_fsm类似的方法不兼容。 添加了如下功能:

1
2
3
4
5
6
7
- keep_state, repeat_state
- callback_mode/1
- postpone
- next_event
- state_enter
- format_status
- etc

behavior

比较gen_fsm和gen_statem的行为。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
gen_fsm module                    Callback module
-------------- ---------------
gen_fsm:start
gen_fsm:start_link -----> Module:init/1

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

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
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
gen_statem module                        Callback module
----------------- ---------------
gen_statem:start
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/4

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

- -----> Module:code_change/4

事实证明,回调正在减少。最重要的是,在gen_statem中,同步/异步的使用是call / cast。StateName / 3在参数的开头采用了事件类型,它来接收调用/强制转换等。info分支也在这里分支。

1
2
3
4
% Module:StateName(EventType, EventContent, Data) -> StateFunctionResult
hello({call, From}, eventname, Data) -> ...;
hello(cast, eventname, Data) -> ...;
hello(info, eventname, Data) -> ...;

EventType有六种类型,包括后面描述的类型。这将处理程序集成到StateName / 3中。

虽然处理程序的返回值有以下几种类型:

1
2
3
{next_state, NextStateName, NewStateData}
{next_state, NextStateName, NewStateData, hibernate}
{next_state, NextStateName, NewStateData, Timeout}

它也可以写为gen_fsm,但可以将多个动作(元组)写为数组。

1
2
3
{next_state, NextStateName, NewStateData}
{next_state, NextStateName, NewStateData, [{hibernate, true}]}
{next_state, NextStateName, NewStateData, [{timeout, Time, Data1}, {state_timeout, Time, Data}2]}

KEEP_STATE

当未在处理程序结束时转换状态时,gen_fsm显式指定了与其自身相同的状态名称。

1
2
hello(Event, Data) ->
{next_state, hello, Data}.

可以使用keep_state明确表达此信息。

1
2
hello(cast, Event, Data) ->
{keep_state, Data}.

如果数据相同,则只有keep_state_and_data就足够了。

1
2
hello(cast, Event, Data) ->
keep_state_and_data.

当您想要执行操作但不希望状态在使用稍后描述的超时等操作时转换时,可以使用此方法。

1
2
hello(cast, Event, Data) ->
{keep_state_and_data, [{timeout, 1000, world}]}.

callback_mode

call_stode / 0的行为已添加到gen_statem中。这指定了如何实现回调,使用像gen_fsm这样的原子命名状态以及实现相应函数的样式state_functions

1
callback_mode() -> state_functions.

handle_event_function

在gen_fsm中,状态以atom命名。但是,如果要将某个数据容器的值视为状态,则将模式匹配为状态而不是原子可能更方便。handle_event_function通过返回callback_mode / 0 ,可以用任何值表示状态。

1
callback_mode() -> handle_event_function.

只有handle_event一种类型的回调,可以通过仅匹配此函数的参数来单独处理。

1
handle_event(EventType, EventContent, State, Data)

State Enter Calls

callback_modestate_enter添加,您可以执行状态State Enter Calls执行一次。

1
2
3
4
5
6
7
callback_mode() ->
[state_functions, state_enter].

StateName(enter, OldState, Data) ->
{keep_state_and_data, [{state_timeout, 5000, SomeState}]};
StateName(cast, OldState, Data) ->
{next_state, SomeState, Data}.

如果启用此模式,则所有事件都需要输入的处理程序实现。

Timeout

gen_statem中有三种主要的超时方法。在服务器实现等中,超时模型非常重要。 它比gen_fsm更强大.。

Timeout Event:

通过返回如下操作来启动事件超时:

1
2
{next_state, NextState, Data, 10000}
{next_state, NextState, Data, [{timeout, 10000, EventContent}]}

如果在1000毫秒内没有下一个事件,则超时事件会上升。

1
NextState(timeout, Context, Data)

无论发生什么事件,都会取消此计时器。

State Timeout Event:

通过返回如下操作来启动状态超时:

1
{next_state, NextState, Data, [{state_timeout, 10000, EventContent}]}

如果状态未在1000毫秒内转换,则超时事件会上升。

1
NextState(state_timeout, Context, Data)

无论转换到什么状态,都会取消此计时器。

Erlang Timer:

如果要独立管理跨越事件或状态更改的独立计时器,使用erlang:start_timer / 3创建计时器并保留它。

1
Timer = erlang:start_timer(1000, self(), Data)

发生此计时器超时时,超时事件将以当前状态上升。

1
2
NextState(info, {timeout, Timer, Data}, Data)
erlang:cancel_timer(Timer)

Postponing

可以推迟已发生事件的操作。一旦状态转换,转发的事件就会排队并按顺序播放。

1
{keep_state, Data, [postpone]}

Self Generated Events

从状态机内部,可以向自己发送事件。在gen_fsm中,除了使用gen_statem发布之外没有其他事件,但是在某些情况下,由于回调处理而生成另一个事件。gen_statem next_event可以通过返回以下操作从内部发出事件。

1
{keep_state, Data, [{reply, From, ok}, {next_event, cast, Arg}]};

所有EventTypes next_event都可以通过,并且可以被视为与外部事件相同。

internal event

internalEventType为,next_event只能通过发送。

1
{keep_state, Data, [{reply, From, ok}, {next_event, internal, Arg}]};

相反,由于它不是internal从外部发送的,因此可以保证它是内部生成的自生成事件。其他EventTypes next_event无法区分为来自外部或来自外部,因此可以在内部关闭事件时使用它们。

Module:format_status/2

可以预处理由sys:get_status / 1,2和teminate转储输出的State值。可选的,因此不必导出。用于在状态较大时仅缩小重要信息,或防止输出重要信息。