`
kenby
  • 浏览: 716860 次
  • 性别: Icon_minigender_1
  • 来自: 北京
社区版块
存档分类
最新评论

Erlang 学习笔记

 
阅读更多

函数的返回值

函数不会显示地返回值,函数中最后一条语句的执行结果将作为函数的返回值。

 

main(_) ->
    {A, B} = test(),
    io:format("A = ~w, B = ~s~n", [A, B]).

test() ->
    {2,"hello"}.

 

 

函数的分支

同一个函数中,并列的逻辑分支之间,用分号 “;” 分界;顺序语句之间,用逗号 “,” 分隔。

 

 

Atom 的用途

Atoms are literals, constants with their own name for value. 

Atom 以小写字母开头, Atom 使程序员只需关注变量的实际意义,而不用关心变量代表的值。例如

使用 blue, green, black 代表 0, 1, 2

 

Erlang 是否有 string

This is why you may have heard Erlang is said to suck at string manipulation: there is no built-in

string type like in most other languages. This is because of Erlang's origins as a language created

and used by telecom companies. They never (or rarely) used strings and as such, never felt like

adding them officially. However, most of Erlang's lack of sense in string manipulations is getting

fixed with time: The VM now natively supports Unicode strings, and overall gets faster on string

manipulations all the time.

 

Erlang 的模块

把一系列函数放在同一个文件,就成为了模块。模块的名字和文件名相同。

在 Erlang 中,每个函数都属于某个模块,调用模块内的函数,可以这样做:

Module:Function(Arguments).

1> erlang:element(2, {a,b,c}).
b
2> element(2, {a,b,c}).

b

erlang 模块会自动导入,所以 element 前面不加 erlang 模块名也可以成功调用。

 

模块的声明

每个文件(模块)的开头都要有一条语句:

-module(Name).

 

用来声明模块,Name 是一个 Atom,指模块的名字。

 

然后再声明此模块内定义了哪些函数:

-export([Function1/Arity, Function2/Arity, ..., FunctionN/Arity]).

Arity 表示函数中参数的个数。

 

最后编译模块的代码,进入 erl 交互环境。

c(module_name).

 

模式匹配 (Pattern Matching)

 

main(_) ->
    greet(male, "Kenby").

greet(male, Name) ->
    io:format("Hello, Mr. ~s!~n", [Name]);
greet(female, Name) ->
    io:format("Hello, Mrs. ~s!~n", [Name]);
greet(_, Name) ->
    io:format("Hello, ~s!~n", [Name]).

 greet 函数由三个函数子句(function clause)组成,第一个参数类型是 Atom

调用 greet 函数,Erlang 会根据第一个参数自动匹配合适的 function clause。 

 

调用函数发生了什么

写一个函数,比较两个 Atom 是否相同

 

same(X,X) ->
    true;
same(_,_) ->
    false.

 调用 same(a, a) 将返回 true。如此简洁的实现,这得溢于 Erlang 的 Pattern Matching 机制。

 函数调用,传参数的时候,如果形参 X 没有绑定值,则把参数值 a 绑定给形参 X,

到了第二个形参 X,此时 X 绑定了值,就检查参数值 a 和形参 X 是否匹配。匹配就调用这个函数。

这里 a 和 X = a 是匹配的,所以调用函数后返回 true。

 

再写一个函数返回 list 的第一个元素。

 

head([H|_]) -> H.

 调用 function:head([1,2,3,4]).将返回 1 . 依然如此简洁。原理同上,

传参数的时候,形参没有绑定,先把参数值绑定给形参

[ H | _] =  [1,2,3,4]

这样 list 的第一个元素就绑定给 H,所以直接返回 H 就可以了。

 

 

Guards 表达式

Erlang 的 Pattern Matching 很方便,但只能根据类型来匹配,不能根据参数值

来匹配调用的函数, Guards 表达式弥补了这个不足。

 

old_enough(X) when X >= 16 -> true;
old_enough(_) -> false.

 这个例子,参数值大于等于 16 才会调用函数返回 true.

 

 

特殊的 else

 

main(_) ->
    out_range(3).

out_range(X) ->
    if
        X > 5 ->
            true;
        X < 1 ->
            true
    end.

 运行出现错误:

 exception error: no true branch found when evaluating an if expression

在 Erlang 的世界里,everything has return something, if 也不例外,但这里

两个 if 分支的 guard 表达式都为 false, 两个分支都无法执行, if 不能返回任何

值,然后就出错了。解决办法是添加 else 分支,捕获其他情况,输出 false 。在

Erlang 中,使用 true 充当 else 的作用。

 

main(_) ->
    out_range(3).

out_range(X) ->
    if
        X > 5 ->
            true;
        X < 1 ->
            true;
        true ->
            false
    end.
 

 

 

并发编程基础

Erlang 并发编程的基础是 3 个原语:

(1) spawning new processes

Erlang 中进程是函数的动态执行,函数执行完,进程就会退出。

进程是轻量级的,spawn 函数用来创建进程,返回新进程的 ID。

 

(2) sending messages

 

Erlang 为每个进程配备 mailbox 用来接受消息。Erlang 发送消息的语法是:

Pid ! Message

把消息 Message 发送给进程, Pid 是接受进程的 ID,一条消息可以发给多个进程:

Pid2 ! Pid1 ! Message

消息先发给 Pid1, 在发给 Pid2。

 

(3) receiving messages

receive 子句接受消息,如果当前 mailbox 是空的,进程将阻塞直到新的消息到来。

收到的消息可以按照 Pattern Matching 执行相应的动作,完了进程就退出,如果

想继续接受消息,则递归调用自己,这样进程就不会退出了。

 

-module(dolphins).
-compile(export_all).

dolphin3() ->
    receive
        {From, do_a_flip} ->
            From ! "How about no?",
            dolphin3();
        {From, fish} ->
            From ! "So long and thanks for all the fish!",
            dolphin3();
        _ ->
            io:format("Heh, we're smarter than you humans.~n"),
            dolphin3()
    end.

 

运行如下:

 

15> Dolphin3 = spawn(dolphins, dolphin3, []).
<0.75.0>
16> Dolphin3 ! Dolphin3 ! {self(), do_a_flip}.
{<0.32.0>,do_a_flip}
17> flush().
Shell got "How about no?"
Shell got "How about no?"
ok
18> Dolphin3 ! {self(), unknown_message}.    
Heh, we're smarter than you humans.
{<0.32.0>,unknown_message}
 

 

Records

 

-record(person, {name, phone, address}).

main(_) ->
    P = #person {
        name = "kenby", 
        phone = "15527766728", 
        address = "国际软件学院"
    },
    io:format("~s,~s,~s~n", 
        [P#person.name, P#person.phone, P#person.address]).

 

Records 类似 C 语言的结构体,提供命名访问的功能, 使用 # 号创建 Records。

Records 不是 Erlang 内置的类型,它只是编译器的小把戏,访问 Records 的属性

时,需要在变量后面再加上 Record 的名字。

 

 

make_ref

返回一个唯一的 ID,只有调用 make_ref 的次数达到 2^82,才有可能出现重复,实际应用中这足够了。

make_ref 一般用于这种场景:

进程 A 调用 make_ref 生成自己的 ID,发送给进程 B, 进程 B 把消息和进程 A 的 ID 都发给 A,

A 确认收到的 ID 和自己的 ID 是一样的,就认为此消息是发给自己的。

 

Monitors

进程 Pid1 调用 erlang:monitor(process, Pid2). 创建进程 Pid2 的

监控,此函数返回一个 Ref。

进程 Pid2 退出的时候会发送一个 'DOWN' 消息给进程 Pid1

{'DOWN', Ref, process, Pid2, Reason}

监控可以这样删除: erlang:demonitor(Ref).

 

Register

进程的 Pid 可以用来操作进程, 还可以把进程注册给一个 Atom, 以后通过 Atom 来操作进程

 

register(Name, Pid) Associates the name Name, an atom, with the process Pid.

 

 

-module(msg).
-compile(export_all).

start() ->
    register(?MODULE, Pid = spawn(?MODULE, msg, [])),
    Pid.

send_msg() ->
    ?MODULE ! {self(), "Hello, World"}.

rcv_msg() ->
    receive
        {From, Message} ->
            io:format("from ~p: ~p~n", [From, Message]),
            msg();
        _ ->
            io:format("unknown.~n"),
            msg()
    end.

 

 

Eshell V5.7.4  (abort with ^G)
1> c(msg).
{ok,msg}
2> msg:start().
<0.42.0>
3> msg:send_msg().
from <0.35.0>: "Hello, World"
{<0.35.0>,"Hello, World"}

 

这个例子把接收消息的进程注册给模块名 msg, 以后通过模块名就能发送消息给进程了。

 

 

OTP

OTP 是 Open Telecom Platform 的简写,不过如今 OTP 和 电话关系不大,更多的

是软件方面的。如果说并发性和分布式是 Erlang 伟大的一半来源, 而容错性是另一半

来源,那么 OTP 就是容错性伟大的又一半来源。

OPT 的原则是把通用代码和逻辑代码分开。尽量重用代码

 

 

gen_server (通用服务器)

gen_server 是 OPT 的一个组件,它定义了自己的一套规范,把编写

服务器的共性代码抽取出来,形成一个通用服务器框架。

使用 gen_server 编写服务器程序的三个步骤: 
1,为 callback module 起个名字 
2,写接口 function (API)
3,在 callback module 里实现6个必需的 callback function

 

gen_server 预定义好了回调函数的机制:

 

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 

 

 

实现 6 个 callback function

(1) gen_server:start_link(Name, Mod, InitArgs, Opts) 注册一个名为 Name 的 server,

       Mod 是 callback module 的名字, 名字注册成功后,回调 Mod:init(InitArgs) 函数启动 server。

       其中 InitArgs 就是传递给 init 的参数

(2) client 端程序调用 gen_server:call(Name, Request) 来调用server,server处理逻辑为handle_call/3 

(3) gen_server:cast(Name, Name)调用 handle_cast(_Msg, State)以改变server状态 
(4) handle_info(_Info, State) 用来处理发给 server 的自发消息 
(5) terminate(_Reason, State) 是 server 关闭时的 callback 
(6) code_change是server热部署或代码升级时做callback修改进程状态 

 

gen_server 的工作流程。

 

客户端通过 API 调用 gen_server:call(Name, Request) 向服务器发出请求,

其实是把请求以消息的形式发送到服务器的 mailbox, 然后 gen_server 内部的 loop

从 mailbox 取出请求,把它交给对应的 handle 去处理。

 

一个 gen_server 的例子。

 

 

%% ---
%%  Excerpted from "Programming Erlang",
%%  published by The Pragmatic Bookshelf.
%%  Copyrights apply to this code. It may not be used to create training material, 
%%  courses, books, articles, and the like. Contact us if you are in doubt.
%%  We make no guarantees that this code is fit for any purpose. 
%%  Visit http://www.pragmaticprogrammer.com/titles/jaerlang for more book information.
%%---
-module(my_bank).

-behaviour(gen_server).
-export([start/0]).
%% gen_server callbacks
-export([init/1, handle_call/3, handle_cast/2, handle_info/2,
        terminate/2, code_change/3]).
-compile(export_all).

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

new_account(Who)      -> gen_server:call(?MODULE, {new, Who}).
deposit(Who, Amount)  -> gen_server:call(?MODULE, {add, Who, Amount}).
withdraw(Who, Amount) -> gen_server:call(?MODULE, {remove, Who, Amount}).

init([]) -> {ok, ets:new(?MODULE,[])}.

handle_call({new,Who}, _From, Tab) ->
    Reply = case ets:lookup(Tab, Who) of
        []  -> ets:insert(Tab, {Who,0}), 
            {welcome, Who};
        [_] -> {Who, you_already_are_a_customer}
    end,
    {reply, Reply, Tab};

handle_call({add,Who,X}, _From, Tab) ->
    Reply = case ets:lookup(Tab, Who) of
        []  -> not_a_customer;
        [{Who,Balance}] ->
            NewBalance = Balance + X,
            ets:insert(Tab, {Who, NewBalance}),
            {thanks, Who, your_balance_is,  NewBalance}	
    end,
    {reply, Reply, Tab};

handle_call({remove,Who, X}, _From, Tab) ->
    Reply = case ets:lookup(Tab, Who) of
        []  -> not_a_customer;
        [{Who,Balance}] when X =< Balance ->
            NewBalance = Balance - X,
            ets:insert(Tab, {Who, NewBalance}),
            {thanks, Who, your_balance_is,  NewBalance};	
        [{Who,Balance}] ->
            {sorry,Who,you_only_have,Balance,in_the_bank}
    end,
    {reply, Reply, Tab};

handle_call(stop, _From, Tab) ->
    {stop, normal, stopped, Tab}.

handle_cast(_Msg, State) -> {noreply, State}.
handle_info(_Info, State) -> {noreply, State}.
terminate(_Reason, _State) -> ok.
code_change(_OldVsn, State, _) -> {ok, State}.

 

 

 

Eshell > c(my_bank).
Eshell > my_bank:start().
Eshell > my_bank:new_account("hideto").
Eshell > my_bank:deposit("hideto", 100).
Eshell > my_bank:deposit("hideto", 200).
Eshell > my_bank:withdraw("hideto", 10).
Eshell > my_bank:withdraw("hideto", 10000).
 

 

gen_fsm(通用有限自动机)

FSM (有限自动机) 可以这样描述:

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

处于状态 S 的 FSM,发生了事件 E, 然后执行动作 A, 把状态转移到 S'

 

如何描述状态?

在 Erlang 中每个状态对应一个同名的函数,函数相当于 Action,它处理

当前状态下发生的事件,然后转移到下一个状态。这样的函数如下所示:

 

 

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

 

StateName 是当前状态,StateData是当前状态下的数据,StateName'是下一个状态,StateData'是新的状态数据。

 

如何转移状态?

向 gen_fsm 发送事件,gen_fsm 就回调当前状态的函数来处理事件,然后转移到下一个状态。

发送事件的两个函数是:

(1) gen_fsm:send_event 

向 gen_fsm 异步地发送事件,然后立即返回,gen_fsm 调用 StateName/2 处理此时间,这个 StateName

就是当前状态对应的函数。

 

(2) gen_fsm:sync_send_event

向 gen_fsm 同步地发送事件,函数不会立即返回,只有当 gen_fsm 产生回复或者超时才返回。

 

gen_fsm 的回调机制

Erlang 的 有限自动机是通过事件驱动,然后回调相应的函数实现的。gen_fsm 已经预定义好了

事件和回调函数的对应关系,如下所示:

 

 

gen_fsm module                    Callback module
--------------                    ---------------
gen_fsm:start_link                -----> Module:init/1
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

 

可以看到,start_link 启一个 gen_fsm 的时候,gen_fsm 会回调 init 函数

send_event 异步发送事件后,gen_fsm 会回调 StateName 函数。

send_all_state_event 向所有状态异步地发送事件后,gen_fsm 会回调 handle_event 函数。

sync_send_event 同步地发送时间后,gen_fsm 会回调 StateName 函数。

sync_send_all_state_event 向所有状态发送同步地发送事件后,gen_fsm 会回调 handle_sync_event 函数。

当 gen_fsm 接受到事件之外的消息时,gen_fsm 会回调 handle_info 函数。

当 gen_fsm 快要退出的时候,gen_fsm 会回调 terminate 函数。

关于 code_change,还没有理解其作用,参考这句话:

This function is called by a gen_fsm when it should update its internal state

data during a release upgrade/downgrade。

 

再论 StateName 函数

前面讲过 StateName 函数和状态是一一对应的,它用来处理状态遇到的事件,然后转移到下一个状态。

在 gen_fsm 中,有两种事件:同步和异步,相应地,处理事件的函数 StateName 也有两种形式:

(1) 处理异步事件的 StateName/2

 

 

Module:StateName(Event, StateData) -> Result
Event = timeout | term()
StateData = term()
Result = 
    {next_state,NextStateName,NewStateData} 
  | {next_state,NextStateName,NewStateData,Timeout}
  | {next_state,NextStateName,NewStateData,hibernate}
  | {stop,Reason,NewStateData}
 NextStateName = atom()
 NewStateData = term()
 Timeout = int()>0 | infinity
 Reason = term()

 

gen_fsm 接收到 send_event 发送的异步事件后,就调用与当前状态同名的 StateName 函数来处理事件。

函数的返回值就作为下一个状态,有四种情况:

a. {next_state,NextStateName,NewStateData}  仅把状态转移到 NextStateName

b. {next_state,NextStateName,NewStateData,Timeout}  把状态转移到 NextStateName,而且规定

    下一个状态在 Timeout 时间内没有接收到事件的话,将发生超时事件。超时事件用 Atom timeout 表示,

     超时事件也会触发 StateName 函数。

c. {next_state,NextStateName,NewStateData,hibernate},把状态转移到 NextStateName, 而且规定

    下一个状态在接收到事件之前,进程将挂起。

d. 如果函数返回 {stop,Reason,NewStateData}, 则 gen_fsm 会调用 terminate 函数终止进程。

 

 (2) 处理同步事件的 StateName/3

 

 

Module:StateName(Event, From, StateData) -> Result

Event = term()
From = {pid(),Tag}
StateData = term()
Result = 
    {reply,Reply,NextStateName,NewStateData}
  | {reply,Reply,NextStateName,NewStateData,Timeout}
  | {reply,Reply,NextStateName,NewStateData,hibernate}
  | {next_state,NextStateName,NewStateData}
  | {next_state,NextStateName,NewStateData,Timeout}
  | {next_state,NextStateName,NewStateData,hibernate}
  | {stop,Reason,Reply,NewStateData} | {stop,Reason,NewStateData}
 Reply = term()
 NextStateName = atom()
 NewStateData = term()
 Timeout = int()>0 | infinity
 Reason = normal | term()

 

 

gen_event

在 Erlang OTP 中,event manager 相当于一个有名字的事件容器, 事件发送给 event manager,

event manager 就会调用相应的 event handler 来处理。不过,你得事先在 event manager 安装

好这些事件处理函数,即 event handlers。 在 event manager 中,可以给一个事件安装多个 event handler。

事件发生后,这些 event handler 将按照安装的先后顺序一个个地调用。

 

如何编写 event handler

event handler 是一个单独的模块,它由一系列 callback function 组成。gen_event 规定了

behavious function 和 callback function 的回调关系如下:

 

gen_event module                   Callback module
----------------                   ---------------
gen_event:start_link       ----->  -
gen_event:add_handler
gen_event:add_sup_handler  ----->  Module:init/1
gen_event:notify
gen_event:sync_notify      ----->  Module:handle_event/2
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

 

 

编写 event handler 其实是把 callback fucntion 都实现一遍,下面 编写一个 event handler ,

用来把错误信息记录到日志文件。代码如下所示:

 

 

-module(file_logger).
-behaviour(gen_event).
-export([init/1, handle_info/2, code_change/3, 
        handle_event/2, handle_call/2, terminate/2]).

%%% callback function
init(File) ->
    {ok, Fd} = file:open(File, [read, write]),
    {ok, Fd}.

handle_event(ErrorMsg, Fd) ->
    io:format(Fd, "*** Error *** ~p~n", [ErrorMsg]),
    {ok, Fd}.

handle_info(_Info, _State) ->
    {ok, _State}.

handle_call(_Request, _State) ->
    {ok, reply, _State}.

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

terminate(_Args, Fd) ->
    file:close(Fd).
 

然后我们编写一个日志系统,它使用上面编写的 event handler 来记录日志信息。代码如下:

 

 

-module(logger).
-export([start_link/0, log_error/1, delete_handler/0]).

%%% client API
start_link() ->
    gen_event:start_link({local, error_man}),
    gen_event:add_handler(error_man, file_logger, "/home/kenby/log.txt").

log_error(Msg) ->
    gen_event:notify(error_man, Msg).

delete_handler() ->
    gen_event:delete_handler(error_man, file_logger, []).

 

该日志系统提供 3 个 API  给用户使用,

start_link 中:

gen_event:start_link 用来创建一个名字为 error_man 的 event manager。

gen_event:add_handler 发送一个消息给 event manager(error_man), 告诉它

把 event handler ( file_logger ) 安装好,event manager 收到此消息后回调 file_logger:init(File)方法,

其中 init 函数的 File 参数来自 add_handler 的第 3 个参数("/home/kenby/log.txt"),init 方法返回

{ok, Fd} 作为事件处理函数的初始状态。

 

log_error 中:

gen_event:notify(error_man, Msg)。 向 event manager(error_man) 发送事件,error_man 收到此

事件后将调用 error_man:handle_event 来处理。

 

delete_handler 中:

gen_event:delete_handler 删除handler (file_logger),这会引起 file_logger:terminate 回调。

 

event_manager 的本质是一个进程,它维护一个由 {Module, State} 组成的 list, 其中 Module 相当于

event handler, State 是事件处理函数的内部状态。event_manager 接收事件后,会把该 list 上所有

的 handler 都调用一遍。需要特别注意的是一个 event_manager 只能管理一个事件,但可以安装多个

handler。

 

 

Supervisor

Supervisor 负责启动,停止和监控子进程, Supervisor 相当于子进程的监护人,它

的作用是保护子进程一直运行着。

 

Supervisor 具体监控哪些子进程是通过 child specifications 列表说明的。子进程按照列表的顺序

启动,然后以相反的顺序终止。

 

例子:使用 Supervisor 启动 my_bank 服务器。

 

 

-module(ch_sup).
-behaviour(supervisor).
-export([start_link/0]).
-export([init/1]).

start_link() ->
    supervisor:start_link(ch_sup, []).

init(_Args) ->
    {ok, {{one_for_one, 1, 60},
            [{my_bank, {my_bank, start, []},
                    permanent, brutal_kill, worker, [my_bank]}]}}.

 

 

supervisor:start_link 创建一个 supervisor 进程, 它回调 ch_sup:init 方法,init 方法干两件事情:

(1)  设置 restart strategy 和 maximum restart frequency

(2) 初始化 child specifications 列表,这个列表指明启动哪些进程。

上面这些信息都是通过 init 函数的返回值直接设置的, 一般 init 函数返回值的格式为:

 

 

init(...) ->
    {ok, {{RestartStrategy, MaxR, MaxT},
          [ChildSpec, ...]}}.

 

其中,RestartStrategy 有三种类型: one_for_one, one_for_all, rest_for_one

关于 MaxR 和 MaxT 参数: Supervisor 内部有一套机制用来限制子进程重启的频率,机制是这样子的:

如果在 MaxT 时间内,发生重启的次数达到了 MaxR 次,则 Supervisor 终止所有子进程,然后把自己也终止。

 

ChildSpec 的格式为:

 

{Id, StartFunc, Restart, Shutdown, Type, Modules}
    Id = term()
    StartFunc = {M, F, A}
        M = F = atom()
        A = [term()]
    Restart = permanent | transient | temporary
    Shutdown = brutal_kill | integer() &gt;=0 | infinity
    Type = worker | supervisor
    Modules = [Module] | dynamic
        Module = atom()

 

 

StartFunc 是启动进程要调用的函数。StartFunc 由模块,函数名,参数组成。

 

除了在 init 函数指定创建哪些子进程,还可以调用 

supervisor:start_child(Sup, ChildSpec)

动态地创建子进程。

 

 

创建 OTP 程序

参考 Building an application with OTP

这篇文章讲述了如何使用 OTP 实现进程池,文章讲的很清楚,这里再补充一下整个程序的流程.

1 首先调用 ppool_supersup 的 start_link 方法创建超级监控进程。

2 调用 ppool_supersup:start_pool 创建 ppool_sub 监控进程,而在

    ppool_sub 的 init 方法中又会创建 ppool_serv 进程,在 ppool_serv

    的 init 方法中向自己发送消息,然后引起 handle_info 得到调用,在

    这里创建 worker_sup 监控进程。之所以要在 ppool_serv 中启动 woker_sup 。

    是因为 ppool_serv 要和 worker_sup 通信,因此要保存 worker_sup 的进程 ID

 

组织 OTP 工程的结构。

 

Emakefile
ebin/
	- ppool.app
include/
priv/
src/
	- ppool.erl
	- ppool_serv.erl
	- ppool_sup.erl
	- ppool_supersup.erl
	- ppool_worker_sup.erl
test/
	- ppool_tests.erl
	- ppool_nagger.erl

 

Emakefile 的内容为:

 

{"src/*", [debug_info, {i,"include/"}, {outdir, "ebin/"}]}.
{"test/*", [debug_info, {i,"include/"}, {outdir, "ebin/"}]}.
 

ppool.app 的内容为:

 

{application, ppool,
 [{vsn, "1.0.0"},
  {modules, [ppool, ppool_serv, ppool_sup, ppool_supersup, ppool_worker_sup]},
  {registered, [ppool]},
  {mod, {ppool, []}}
 ]}.
 

运行程序:

 

erl -make
erl -pa ebin/
application:start(ppool).
ppool:start_pool(nag, 2, {ppool_nagger, start_link, []}).
ppool:run(nag, ["finish the chapter!", 500, 10, self()]).
ppool:run(nag, ["Watch a good movie", 500, 10, self()]).
flush().

 

 

Behaviour

Remember that behaviours are always about splitting generic code away from specific code. 

  • 大小: 16.8 KB
分享到:
评论

相关推荐

Global site tag (gtag.js) - Google Analytics