Erlang源碼閱讀筆記之gen_server進程啓動

概述

gen_server大概是用的最爲普遍的OTP組件了,它提供了通用服務進程的生命週期骨架(進程啓動、事件循環、消息處理以及終止)和協做API,並經過behaviour規定了各個週期回調函數的形態,只要咱們按照gen_server behaviour實現回調函數,就能將其歸入到OTP的服務管理體系當中。spring

gen_server文檔中API與各回調函數的對應關係:api

gen_server module api callback function
gen_server:start Module:init/1
gen_server:start_link Module:init/1
gen_server:stop Module:terminate/2
gen_server:call Module:handle_call/3
gen_server:multi_call Module:handle_call/3
gen_server:cast Module:handle_cast/2
gen_server:abcast Module:handle_cast/2
- Module:handle_info/2
- Module:terminate/2
- Module:code_change/3

下面咱們就按照其運行的生命週期,來看一下具體的實現。app

進程啓動

能夠看到,gen_server在對外API中提供了start(start/3,start/4)和start_link(start_link/3, start_link/4)兩組函數,start和start_link的最大區別在於,start啓動的是一個單獨的進程,不會加入到任何OTP監控樹中,而start_link是能夠被加入到監控樹中的(固然得由Supervisor去啓動才能夠,二者創建link關係)。平時用的最多的會是start_link,而start基本場景是對某個模塊進行單獨的測試。函數

啓動函數的實現:oop

start(Mod, Args, Options) ->
    gen:start(?MODULE, nolink, Mod, Args, Options).

start(Name, Mod, Args, Options) ->
    gen:start(?MODULE, nolink, Name, Mod, Args, Options).

start_link(Mod, Args, Options) ->
    gen:start(?MODULE, link, Mod, Args, Options).

start_link(Name, Mod, Args, Options) ->
    gen:start(?MODULE, link, Name, Mod, Args, Options).

能夠看到四個函數都是將請求轉發到了gen模塊的start函數,並且start和start_link的差異在於第二個參數,start是nolink,start_link是link,再看看gen模塊作了什麼:性能

start(GenMod, LinkP, Name, Mod, Args, Options) ->
    case where(Name) of
	undefined ->
	    do_spawn(GenMod, LinkP, Name, Mod, Args, Options);
	Pid ->
	    {error, {already_started, Pid}}
    end.

start(GenMod, LinkP, Mod, Args, Options) ->
    do_spawn(GenMod, LinkP, Mod, Args, Options).

do_spawn(GenMod, link, Mod, Args, Options) ->
    Time = timeout(Options),
    proc_lib:start_link(?MODULE, init_it,
			[GenMod, self(), self(), Mod, Args, Options], 
			Time,
			spawn_opts(Options));
do_spawn(GenMod, _, Mod, Args, Options) ->
    Time = timeout(Options),
    proc_lib:start(?MODULE, init_it,
		   [GenMod, self(), self, Mod, Args, Options], 
		   Time,
		   spawn_opts(Options)).

do_spawn(GenMod, link, Name, Mod, Args, Options) ->
    Time = timeout(Options),
    proc_lib:start_link(?MODULE, init_it,
			[GenMod, self(), self(), Name, Mod, Args, Options],
			Time,
			spawn_opts(Options));
do_spawn(GenMod, _, Name, Mod, Args, Options) ->
    Time = timeout(Options),
    proc_lib:start(?MODULE, init_it,
		   [GenMod, self(), self, Name, Mod, Args, Options], 
		   Time,
		   spawn_opts(Options)).

...

timeout(Options) ->
    case lists:keyfind(timeout, 1, Options) of
	{_,Time} ->
	    Time;
	false ->
	    infinity
    end.

兩個start的區別在於一個有Name,一個沒有,若是指定了Name,會經過where函數檢查進程是否已經註冊了:測試

where({global, Name}) -> global:whereis_name(Name);
where({via, Module, Name}) -> Module:whereis_name(Name);
where({local, Name})  -> whereis(Name).

where會分三種狀況去查詢,global、via和local,global和local很好理解,{via, Module, Name}這個會是用在啥場景?思考了半天,覺着多是這樣,有時候可能global並不能知足咱們的要求,好比多是由於性能問題或者應用須要有自定義的namespace規則,這個時候咱們就須要本身來實現服務發現邏輯了(好比經過zookeeper或者etcd),那麼此時就能夠經過這種via的方式,定義一個像global那樣提供服務查詢接口以的模塊來進行查找,這塊兒有OO語言裏面Ducking Type的影子,哈哈。spa

而後link跟no_link的差異在於link是經過proc_lib:start_link建立的進程,而no_link是經過proc_lib:start建立的進程。這倆函數以前已經看過,都是經過同步的方式來建立新進程,只不過start_link創建的是link關係(這也是經過這種方式能加入OTP監控樹的緣由),而start創建的是monitor關係。hibernate

另外能夠看到,咱們在這裏可以經過Option參數來控制新建進程的生存時間,lists:keyfind(Key, N, TupleList)是一個頗有意思的函數,它檢查一個由元組所構成的列表TupleList,若是其中一個元組的第N個元素爲Key,那麼就會返回,若是找不到會返回false,這個在解析配置時會很方便:prototype

1> L = [{a, 1}, {b, 2}, {c, 3}].
[{a,1},{b,2},{c,3}]
2> lists:keyfind(b, 1, L).
{b,2}

接下來重點看新建進程邏輯init_it作了什麼了,gen模塊裏面的init_it實現:

init_it(GenMod, Starter, Parent, Mod, Args, Options) ->
    init_it2(GenMod, Starter, Parent, self(), Mod, Args, Options).

init_it(GenMod, Starter, Parent, Name, Mod, Args, Options) ->
    case register_name(Name) of
	true ->
	    init_it2(GenMod, Starter, Parent, Name, Mod, Args, Options);
	{false, Pid} ->
	    proc_lib:init_ack(Starter, {error, {already_started, Pid}})
    end.

init_it2(GenMod, Starter, Parent, Name, Mod, Args, Options) ->
    GenMod:init_it(Starter, Parent, Name, Mod, Args, Options).

...

register_name({local, Name} = LN) ->
    try register(Name, self()) of
	true -> true
    catch
	error:_ ->
	    {false, where(LN)}
    end;
register_name({global, Name} = GN) ->
    case global:register_name(Name, self()) of
	yes -> true;
	no -> {false, where(GN)}
    end;
register_name({via, Module, Name} = GN) ->
    case Module:register_name(Name, self()) of
	yes ->
	    true;
	no ->
	    {false, where(GN)}
    end.

發現gen模塊的init_it就作了一點微小的事情:沒有指定服務進程名稱,甩鍋給GenMod(在這裏也就是gen_server)的init_it;若是指定了服務進程名,則對服務進行註冊並檢查以前是否註冊過,註冊過直接終止,沒註冊過則甩鍋給gen_server的init。在這裏咱們能夠看見gen模塊進程建立的一個邏輯,若是咱們爲進程指定了Name,那麼在相應的範圍(local、via、global)只能建立一個進程,若是不指定Name,那麼進程建立數目會不受限制。

忽然感受這裏跟Spring的Bean scope很像,好比spring中的Bean能夠是application範圍單例的(就至關於這裏的global),也能夠是用戶會話級別的,也能夠是請求級別的,或者是prototype,每次用到都建立最新的實例,還能夠自定義bean scope,有意思!

既然gen模塊都最終把鍋甩給了gen_server的init_it,那咱們再回頭看看gen_server中init_it的實現:

init_it(Starter, self, Name, Mod, Args, Options) ->
    init_it(Starter, self(), Name, Mod, Args, Options);
init_it(Starter, Parent, Name0, Mod, Args, Options) ->
    Name = gen:name(Name0),
    Debug = gen:debug_options(Name, Options),
    HibernateAfterTimeout = gen:hibernate_after(Options),

    case init_it(Mod, Args) of
	{ok, {ok, State}} ->
	    proc_lib:init_ack(Starter, {ok, self()}), 	    
	    loop(Parent, Name, State, Mod, infinity, HibernateAfterTimeout, Debug);
	{ok, {ok, State, Timeout}} ->
	    proc_lib:init_ack(Starter, {ok, self()}), 	    
	    loop(Parent, Name, State, Mod, Timeout, HibernateAfterTimeout, Debug);
	{ok, {stop, Reason}} ->
	    gen:unregister_name(Name0),
	    proc_lib:init_ack(Starter, {error, Reason}),
	    exit(Reason);
	{ok, ignore} ->
	    gen:unregister_name(Name0),
	    proc_lib:init_ack(Starter, ignore),
	    exit(normal);
	{ok, Else} ->
	    Error = {bad_return_value, Else},
	    proc_lib:init_ack(Starter, {error, Error}),
	    exit(Error);
	{'EXIT', Class, Reason, Stacktrace} ->
	    gen:unregister_name(Name0),
	    proc_lib:init_ack(Starter, {error, terminate_reason(Class, Reason, Stacktrace)}),
	    erlang:raise(Class, Reason, Stacktrace)
    end.
init_it(Mod, Args) ->
    try
	{ok, Mod:init(Args)}
    catch
	throw:R -> {ok, R};
	Class:R -> {'EXIT', Class, R, erlang:get_stacktrace()}
    end.

整體上看來,就是回調業務模塊的init函數,等待其返回結果並進行判斷,決定下一步的走向。若是init返回正常,就會向父進程發送ack消息(父進程會結束阻塞,也就是說,在init執行完畢前,父進程一直是阻塞的),而後以帶超時和不帶超時兩種不一樣的方式進入到事件循環,若是是其它狀況,則會取消註冊,向父進程發送ack消息,而後按狀況拋出不一樣的異常。

相關文章
相關標籤/搜索