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消息,而後按狀況拋出不一樣的異常。