天天看點

《gen_server.erl源碼》

OTP源碼分析。

erlang程式員研究OTP,如同C++程式員研究STL一樣重要。

今天原本想看看OTP的源碼,google了一把,有個家夥的圖畫的實在是賞心悅目。于是,稍做編輯,照搬了過來。

init的調用順序

為什麼從gen_server它開始, 因為gen_fsm和它很類似, 而supervsisor本身是一個gen_server.

弄清楚init函數調用時機非常重要:是一個同步調用!上層調用要同步等待子程序被建立出來,并且成功的執行完了init函數。

init

《gen_server.erl源碼》

圖示為一個叫Mod的子產品, 它是一個gen_server程式, 綠色方格為調用程序(客戶程序), 黃色方格為spawn出的gen程序(服務程序). 不同的泳道表示函數所隸屬的子產品, 通過這個圖可以清晰的看出各個子產品至之間的互相調用, 圖是使用gliffy所畫。

從左上角的start(Args)開始,gen_server的程式Mod的開始函數都會調用gen_server:start(或者start_link)來建立一個服務程序,gen_server:start内部實際上調用的是gen子產品。

gen子產品是很多behaviour的基礎,對于gen程序的啟動和同步操作進行了封裝.gen子產品使用proc_lib:start來更加安全的spawn出gen程序,然後阻塞在sync_wait這個函數裡,等待gen程序的回應.

spawn出的gen程序會執行到Mod子產品的init函數。然後會把{ok, Pid}發送給調用程序告知gen程序已經完成準備工作,然後就進入了自己的循環函數loop中,等待調用程序的下次消息。

調用程序接收到{ok,Pid}之後從阻塞裡退出并傳回。

整個過程簡單,但是要清楚哪個函數是被調用程序或者gen程序所執行。

另外要注意start函數的參數是Args,而init函數的入參是[Args],這個很容易出錯的地方。

可以調用start/4或者start_link/4給啟動的gen程序命名,函數name_register實際調用register函數.

init_it(GenMod, Starter, Parent, Name, Mod, Args, Options) ->
    case name_register(Name) of
    true ->
        init_it2(GenMod, Starter, Parent, Name, Mod, Args, Options);      

cast調用以及傳回值

cast:這是個異步的調用,不會阻塞。沒有傳回值。

《gen_server.erl源碼》

調用程序向gen程序發送cast消息,消息發送之後調用程序并不等待gen程序的消息回饋.

gen程序從loop函數處接受到Request消息,模式比對後一路執行到Mod:handle_cast函數,處理消息之後,gen程序繼續遞歸執行loop函數等待後續的新消息. 注意handle_cast不能傳回{reply,…},否則gen程序會報錯退出。

call調用以及傳回值

call:這是個同步的調用,會阻塞。同時會得到gen_server:handle_call的傳回值。

《gen_server.erl源碼》

調用程序向gen程序發送call消息,和cast不同,調用程序會阻塞在wait_resp_mon函數裡等待gen程序的回饋。收到消息後,gen程序會執行Mod:handle_call函數,并把執行的結果Reply直接發送給調用程序,然後自己再次進入循環等待新的消息。

wait_resp_mon(Node, Mref, Timeout) ->
    receive
    {Mref, Reply} ->
        erlang:demonitor(Mref, [flush]),
        {ok, Reply};
    {'DOWN', Mref, _, _, noconnection} ->
        exit({nodedown, Node});
    {'DOWN', Mref, _, _, Reason} ->
        exit(Reason)
    after Timeout ->
        erlang:demonitor(Mref),
        receive
        {'DOWN', Mref, _, _, _} -> true
        after 0 -> true
        end,
        exit(timeout)
    end.      

預設情況下handle_call的傳回是{reply,….}. 而調用程序阻塞在wait_resp_mon的預設逾時時間是5s(-define(default_timeout, 5000)).

在spec裡看到handle_call的傳回值可以是{noreply,…}, 或者gen程序在處理其它事情而達到逾時時間, 則調用程序會異常退出, 你也可以在調用gen_server:call/3來設定一個call指令的逾時時間.

對于call和cast的指令既可以使用Name也可以使用Pid, gen内部會進行Name到Pid的轉化。

call(Name, Label, Request, Timeout)
  when is_atom(Name), Timeout =:= infinity;
       is_atom(Name), is_integer(Timeout), Timeout >= 0 ->
    case whereis(Name) of
    Pid when is_pid(Pid) ->
        do_call(Pid, Label, Request, Timeout);      

info調用以及傳回值

info:帶外消息,除了消息的格式,其餘的行為和cast一樣。異步的調用,不會阻塞。沒有傳回值。

《gen_server.erl源碼》

可以給gen程序發送任何格式的資訊,這類資訊沒有gen标簽,gen程序接收到這類消息會調用gen_server:handle_info來處理消息,處理過程與cast消息一樣都不能回報結果給調用程序,是以如果handle_info傳回{reply,…}也會導緻gen程序報錯退出。

terminate調用以及傳回值

terminate:異步。

《gen_server.erl源碼》

在handle_*等回調函數處都可以傳回{stop,…} 這樣使得gen程序執行Mod:terminate, 進行程序退出前的收尾工作, 然後回到主循環gen程序再退出.

繼續閱讀