OTP源碼分析。
erlang程式員研究OTP,如同C++程式員研究STL一樣重要。
今天原本想看看OTP的源碼,google了一把,有個家夥的圖畫的實在是賞心悅目。于是,稍做編輯,照搬了過來。
init的調用順序
為什麼從gen_server它開始, 因為gen_fsm和它很類似, 而supervsisor本身是一個gen_server.
弄清楚init函數調用時機非常重要:是一個同步調用!上層調用要同步等待子程序被建立出來,并且成功的執行完了init函數。
init
![](https://img.laitimes.com/img/__Qf2AjLwojIjJCLyojI0JCLicmbw5CN5YDOjVzNiFjZ5kTZhRTO2gTN0YjNiZGM2ETZwUzN28CX5d2bs92Yl1iclB3bsVmdlR2LcNWaw9CXt92Yu4GZjlGbh5yYjV3Lc9CX6MHc0RHaiojIsJye.png)
圖示為一個叫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程序發送cast消息,消息發送之後調用程序并不等待gen程序的消息回饋.
gen程序從loop函數處接受到Request消息,模式比對後一路執行到Mod:handle_cast函數,處理消息之後,gen程序繼續遞歸執行loop函數等待後續的新消息. 注意handle_cast不能傳回{reply,…},否則gen程序會報錯退出。
call調用以及傳回值
call:這是個同步的調用,會阻塞。同時會得到gen_server:handle_call的傳回值。
調用程序向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程序發送任何格式的資訊,這類資訊沒有gen标簽,gen程序接收到這類消息會調用gen_server:handle_info來處理消息,處理過程與cast消息一樣都不能回報結果給調用程序,是以如果handle_info傳回{reply,…}也會導緻gen程序報錯退出。
terminate調用以及傳回值
terminate:異步。
在handle_*等回調函數處都可以傳回{stop,…} 這樣使得gen程序執行Mod:terminate, 進行程序退出前的收尾工作, 然後回到主循環gen程序再退出.