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. 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章