новости сообщество форум вики
Статья   Обсуждение   Просмотр   История  

OTP Gen Fsm


 
Оригинал этой статьи находится по адресу Gen_Fsm Behaviour

Содержание

Поведение Gen_Fsm

Автор: Mirrorer

Дата: 29.11.2006

Версия: 1.01


Эту главу следует читать вместе с документацией по gen_fsm(3), там более подробно описаны все интерфейсные функции и функции обратного вызова.

Конечные автоматы

Конечный автомат (КА) может быть описан как множество отношений вида: State(S) x Event(E) -> Actions(A), State(S')

Если мы находимся в состоянии S и происходит событие E, мы должны выполнить действие A, и перейти в состояние S'.

Для КА, реализованного с помощью поведения gen_fsm, правила преобразования состояний записываюся в виде Erlang функций, соответствующих следующему соглашению:

StateName(Event, StateData) ->
   .. code for actions here ...
   {next_state, StateName', StateData'}

Пример

Дверь с кодовым замком можно рассматривать как КА. В исходном состоянии дверь закрыта. В любой момент кто-то нажимает кнопку, что приводит к возникновению события. В зависимости от того, какие кнопки были нажаты раньше, последовательность может быть правильной, неполной или неправильной. Если последовательность правильная, дверь разблокируется на 30 секунд (30000 мс). Если последовательность неполная, мы ожидаем следующего нажатия кнопки. Если последовательность неправильная, мы все начинаем сначала, ожидая нового нажатия кнопки. Реализация КА для кодового замка, используя gen_fsm находится в этом модуле обратного вызова:

-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}.

do_lock() -> 
   io::format("door closed").

do_unlock() -> 
   io::format("door opened").
   

Этот код более подробно описывается в следующих разделах.

Запуск Gen_Fsm

(Code): В примере из предыдущего раздела gen_fsm запускается вызовом code_lock:start_link(Code):

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

start_link вызывает функцию gen_fsm:start_link/4.

Эта функция создает новый процесс gen_fsm, а также создает связь с ним.

  • Первый аргумент, {local, code_lock}, определяет имя. В данном случае gen_fsm будет локально зарегистрирован как code_lock. Если имя пропущено, gen_fsm не будет зарегистрирован. В этом случае вместо имени необходимо будет использовать его pid. Имя также может быть зарегистрировано как {global, Name}, в этом случае gen_fsm регистрируется с помощью global:register_name/2.
  • Второй аргумент, code_lock, определяет имя модуля обратного вызова, того модуля, в котором находятся функции обратного вызова. В этом случае, интерфейсные функции (start_link и button) находятся в том же модуле, что и функции обратного вызова (init, locked и open). Это хорошая программистская практика – держать код, относящийся к одному процессу, в одном модуле.
  • Третий аргумент, Code, это терм, который передается без изменений в функцию обратного вызова init. Таким образом, init получает правильный код для замка.
  • Четвертый аргумент, [], это список опций. Обратитесь к документации по See gen_fsm(3), чтобы узнать о возможных опциях.

Если регистрация имени проходит успешно, новый процесс gen_fsm вызывает функцию обратного вызова code_lock:init(Code). Эта функция должна вернуть {ok, StateName, StateData}, где {ok, StateName, StateData} – имя начального состояния gen_fsm. В нашем случае это locked, что говорит о том, что мы начинаем работу с дверью, находящейся в закрытом состоянии. StateData – это внутреннее состояние gen_fsm. (Для gen_fsm-ов внутренее состояние часто называется «данными состояния», для того чтобы отличать его от состояний конечного автомата). В этом случае данные состояния – это последовательность нажатых кнопок (работа начинается с пустой последовательности) и правильный код замка.

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

Заметьте, что функция gen_fsm:start_link – синхронная. Она не возвращает значение до тех пор, пока gen_fsm не будет проинициализирован, и не будет готов принимать уведомления.

gen_fsm:start_link может быть использован, если gen_fsm является частью дерева контроля, т.е. запускается контролером. Существует другая функция gen_fsm:start, для запуска gen_fsm в самостоятельном режиме, т.е. для gen_fsm, который не является частью дерева контроля.

Уведомление о событиях

Функция, уведомляющая кодовый замок о нажатии кнопки, реализована с использованием функции 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 – это новое значение для данных состояния gen_fsm.

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}.

Таймауты

При введении правильного кода дверь открывается, и следующий кортеж возвращается из функции locked/2:

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

30000 – это интервал таймаута в миллисекундах. После 30000 мс, т.е. 30 секунд, происходит таймаут. После этого вызывается StateName(timeout, StateData). В этом случае таймаут происходит, когда дверь находится в состоянии open на протяжении 30 секунд. После этого дверь запирается снова:

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

События для всех состояний

Иногда событие может прийти в любом состоянии 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}.

где _StateName - это то состояние в котором пребывал fsm на момент события stop

Завершение работы

В дереве контроля

Если gen_fsm является частью дерева контроля, функция завершения работы не требуется. Gen_fsm будет автоматически завершен контролером. Как именно это будет сделано, определяется стратегией завершения (shutdown strategy), установленной в контролере.

Если необходимо сделать очистку ресурсов перед завершением, в стратегии завершения должно быть установлено значение таймаута, и указанный gen_fsm должен быть настроен на отлавливание сигналов завершения в функции init. Тогда, при завершении работы, gen_fsm вызовет функцию обратного вызова terminate(shutdown, StateName, StateData):

init(Args) ->
   ...,
   process_flag(trap_exit, true),
   ...,
   {ok, StateName, StateData}.

...

terminate(shutdown, StateName, StateData) ->
   ..code for cleaning up here..
   ok.

Автономные Gen_Fsm-ы

Если gen_fsm не является частью дерева контроля, функция завершения работы может быть полезной, например :

...
-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. Это приведет к вызову gen_fsm-ом функции terminate(normal,StateName,StateData1) и последующему завершению.

Обработка других сообщений

Если gen_fsm должен иметь возможность обрабатывать другие сообщения окромя событий, то для их обработки должна быть реализована функция обратного вызова handle_info(Info, StateName, StateData). Примером других сообщений являются сообщения о завершении работы, если gen_fsm связан с другим процессом (окромя контролера) и отлавливает сигналы завершения работы.

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

так же

Личные инструменты
Представиться системе



 

 

Навигация

документация

внешние ссылки

друзья

прочее

Инструменты