- 在Erlang/OTP中有一个基本概念叫监督树。这是一种建立在督程与佣程思想上的进程结构化模型。
- 佣程(worker)是进行计算的进程,也就是说,它们进行实际的工作。
- 督程(supervisor)是监视工作者行为的进程。监督者可以重启工作者如果出现了什么问题.
- 监督树是一种将代码分成监督者和工作者的层次安排,这样才能设计和编写可容错的软件。
上图中,方框提供监督,圆圈是工作者。
行为
在监督树中,很多进程有着相似结构,遵循类似的模式。例如,督程的结构都很相 似。他们之间的唯一区别在于所监督的子进程。此外,很多佣程都是处于服务器-客户端关系中的服务器,有限状态机或者诸如错误日志这样的事件处理器。
行为是对这些常见模式的形式化。其思想是将一个进程的代码划分为一个通用的部分(行为模块)和一个特定的部分(回调模块)。
行为模块是Erlang/OTP的一部分。要实现一个督程,用户只需要实现回调模块,导出预定义集合中的函数—— 回调函数 。
一个例子可以用来说明代码是如何被划分成为通用和特定部分的:考虑下面的代码(普通Erlang编写),一个简单的服务器,用于保持跟踪一些“频道”。其他进程可以通过调用 alloc/0 和 free/1 函数来相应地分配和释放一个频道。
%%ch2.erl:
-module(ch2).
-export([start/0]).
-export([alloc/0, free/1]).
-export([init/0, handle_call/2, handle_cast/2]).
start() ->
server:start(ch2).
alloc() ->
server:call(ch2, alloc).
free(Ch) ->
server:cast(ch2, {free, Ch}).
init() ->
channels().
handle_call(alloc, Chs) ->
alloc(Chs). % => {Ch,Chs2}
handle_cast({free, Ch}, Chs) ->
free(Ch, Chs). % => Chs2
channels() ->
{_Allocated = [], _Free = lists:seq(1,100)}.
alloc({Allocated, [H|T] = _Free}) ->
{H, {[H|Allocated], T}}.
free(Ch, {Alloc, Free} = Channels) ->
case lists:member(Ch, Alloc) of
true ->
{lists:delete(Ch, Alloc), [Ch|Free]};
false ->
Channels
end.
服务器的代码可以重写为一个通用的部分 server.erl:
-module(server).
-export([start/1]).
-export([call/2, cast/2]).
-export([init/1]).
start(Mod) ->
spawn(server, init, [Mod]).
call(Name, Req) ->
Name ! {call, self(), Req},
receive
{Name, Res} ->
Res
end.
cast(Name, Req) ->
Name ! {cast, Req},
ok.
init(Mod) ->
register(Mod, self()),
State = Mod:init(),
loop(Mod, State).
loop(Mod, State) ->
receive
{call, From, Req} ->
{Res, State2} = Mod:handle_call(Req, State),
From ! {Mod, Res},
loop(Mod, State2);
{cast, Req} ->
State2 = Mod:handle_cast(Req, State),
loop(Mod, State2)
end.
- 注意以下几点:
- server 中的代码可以被重用于建立很多不同的服务器端。
- 服务器的名字——这个例子中为原子 ch2——对于客户端函数的用户而言是隐藏的。这意味无须影响客户端就可以改变名字。
- 协议(发给服务器和从服务器接收到的消息)也是隐藏的。这是很好的编程实践,让我们可以在不改变接口函数的代码的情况下改变协议。
- 我们可以扩展服务器 server 的功能,而不用改变 ch2 或任何其它的回调模块。
---------------------------------------------------------------
这个服务器可以用 gen_server 进行重写,结果产生这个回调模块:
-module(ch3).
-behaviour(gen_server).
-export([start_link/0]).
-export([alloc/0, free/1]).
-export([init/1, handle_call/3, handle_cast/2]).
start_link() ->
gen_server:start_link({local, ch3}, ch3, [], []).
alloc() ->
gen_server:call(ch3, alloc).
free(Ch) ->
gen_server:cast(ch3, {free, Ch}).
init(_Args) ->
{ok, channels()}.
handle_call(alloc, _From, Chs) ->
{Ch, Chs2} = alloc(Chs),
{reply, Ch, Chs2}.
handle_cast({free, Ch}, Chs) ->
Chs2 = free(Ch, Chs),
{noreply, Chs2}.
-------------------------------------------------------------------
gen_server 的交互序列
- 进程如何启动
- 如何处理同步请求 Synchronous Requests - Call
- 如何处理异步请求 Asynchronous Requests - Cast
- 通用消息处理 handle_info
- 如何处理进程终止
- 如何进行代码版本替换
gen_server module Callback module
-----------------
---------------
gen_server:start_link -----> Module:init/1
gen_server:call
gen_server:multi_call -----> Module:handle_call/3
gen_server:cast
gen_server:abcast -----> Module:handle_cast/2
- -----> Module:handle_info/2
- -----> Module:terminate/2
- -----> Module:code_change/3