代码改变世界

Erlang OTP设计原则的行为理解

2012-05-12 23:52  rhinovirus  阅读(2662)  评论(0编辑  收藏  举报

1. 首先我们来看例子1。

-module(ch1).
-export([start/0]).
-export([alloc/0, free/1]).
-export([init/0]).
%% 启动频道服务
start()
-> spawn(ch1, init, []). %% 创建一个进程,这个进程会调用init方法,参数为[]
%% 获取一个空闲的频道 alloc()
-> ch1 ! {self(), alloc}, receive {ch1, Res} -> Res end.
%% 释放一个频道 free(Ch)
-> ch1 ! {free, Ch}, ok.
%% 初始化频道信息 init()
-> register(ch1, self()), %% 注册名称为ch1的进程 Chs = channels(), %% 调用channels方法获取 {_Allocated = [], _Free = [1,2,3,4,5,...,100]}. _Allocated 列表表示已经占用的频道,_Free 列表表示空闲的频道 loop(Chs). loop(Chs) -> receive {From, alloc} -> %% 接受到一个获取频道消息 {Ch, Chs2} = alloc(Chs), %% 调用alloc/1方法,获取一个未被占用的频道 From ! {ch1, Ch}, %% 发送一个消息到调用者 loop(Chs2); %% 继续等待消息 {free, Ch} -> %% 接受到一个释放频道Ch的消息 Chs2 = free(Ch, Chs), %% 调用free/2,将该频道放入空闲频道列表中 loop(Chs2) %% 继续等待消息 end.
%% 初始化已占用频道列表和空闲频道列表
channels
() -> {_Allocated = [], _Free = lists:seq(1,100)}.
%% 获取一个空闲频道,并放入已占用频道列表中 alloc({Allocated, [H|T] = _Free}) -> {H, {[H|Allocated], T}}.
%% 将频道Ch从已占用频道列表中移除,放入空闲列表中 free(Ch, {Alloc, Free} = Channels) -> case lists:member(Ch, Alloc) of true -> {lists:delete(Ch, Alloc), [Ch|Free]}; false -> Channels end.

上面那个例子中,主要实现的是 频道服务 功能。用户通过 alloc/0 方法获取一个空闲频道, 通过free/1 去释放一个频道。我已经添加了比较详细的注释

2. 接下来,我们再看一个例子

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

我们将例子1中 能够通用的 代码剥离,所谓能够通用就是,如果我们要实现另一个服务器模型,也可以使用 server 服务器来实现。

另一个好处就是,例子1 中,如果你要添加一个消息类型,则需要修改服务器loop函数,而 例子2中,只需要修改定制部分的功能,和新增回调函数就能实现,

那么这样就能保证服务器的通用,稳定。

模块 server 对应的是极大简化了的Erlang/OTP中的 gen_server 行为。

标准 Erlang/OTP 行为有:

gen_server
用于实现 C/S 结构中的服务端。
gen_fsm
用于实现有限状态机。
gen_event
用于实现事件处理功能。
supervisor
用于实现监督树中的督程。