相信用過erlang的同學(xué)對gen_server并不陌生笛谦,我們在日常使用中,和gen_server打交道的次數(shù)也是最多的当凡。至于用法我這邊也不會詳細(xì)的說明儒老,只是略微帶過蝴乔,我會將篇幅用在更加不常用但是卻很有用的功能上。
1.一般用法以及原理
call(Name,Request,Timeout)實際是向目標(biāo)proc發(fā)送了{'$gen_call',{self(), Mref}, Request}}
的消息
cast(Name,Request)實際是向目標(biāo)proc發(fā)送了{'$gen_cast',{self(), Mref}, Request}}
的消息
這些只不過是gen做的更通用語法糖而已call(Process, Label, Request)
在處理消息的時候驮樊,并沒有先后順序,而是從message box依次取出消息進行處理片酝,遇到{'$gen_call',{self(), Mref}, Request}}
這類消息囚衔,丟給回調(diào)模塊的handle_call
處理,遇到{'$gen_cast',{self(), Mref}, Request}}
這類消息丟給回調(diào)模塊的handle_cast
處理雕沿,剩下的不能識別的消息交給回調(diào)模塊的handle_info
處理练湿,當(dāng)然這里有一個前提,不是系統(tǒng)消息审轮,比如suspend,resume
等肥哎,這類消息是另外的處理邏輯,這里代碼我就不貼了疾渣。
2.合理的使用timeout
2.1 使用timeout可以做超時工作(timer,TTL)
使用場景篡诽,例如timer.erl
的定時任務(wù)
在handle_call
的返回值中返回{reply,Reply,NewState,Timeout}
可以在Timeout時間內(nèi)收不到消息的情況下,自己主動收到一個timeout
的消息,timer就是根據(jù)這一原理打造的榴捡,從而觸發(fā)想要執(zhí)行的MFA
但是同時有一個隱藏的問題存在杈女,那就是系統(tǒng)消息,在處理系統(tǒng)消息之后吊圾,它并沒有修改超時時間达椰,導(dǎo)致收到timeout消息滯后,舉個例子项乒,如何來讓timer出現(xiàn)上述問題啰劲。之前項目組使用的第三方crontab插件(https://github.com/b3rnie/crontab)也會有這個問題。如果要克服這個問題檀何,不能以gen_server為組件蝇裤。
Eshell V10.2 (abort with ^G)
1> G = fun()->
1> F = fun() -> receive Msg -> io:format("~p rev ~p ~n", [erlang:localtime(), Msg]) end end,
1> P = erlang:spawn(F),
1> timer:start(),
1> io:format("now ~p ~n",[erlang:localtime()]),
1> timer:send_after(1000 * 5, P, {msg, erlang:localtime()}),
1> sys:suspend(timer_server),
1> receive
1> after 1000 * 7 -> ok
1> end,
1> sys:resume(timer_server) end.
#Fun<erl_eval.20.128620087>
2> G().
now {{2019,11,21},{23,5,25}}
ok
% 按道理會打印出 {{2019,11,21},{23,5,30}} rev ....廷支,但是由于timer_server被掛起了,所以延后了猖辫。
{{2019,11,21},{23,5,37}} rev {msg,{{2019,11,21},{23,5,25}}}
3>
源代碼分析:
decode_msg(Msg, Parent, Name, State, Mod, Time, HibernateAfterTimeout, Debug, Hib) ->
case Msg of
{system, From, Req} ->
sys:handle_system_msg(Req, From, Parent, ?MODULE, Debug,
% 問題出在這里K峙ⅰ!啃憎!這里的Time應(yīng)該減去操作用的時間
[Name, State, Mod, Time, HibernateAfterTimeout], Hib);
{'EXIT', Parent, Reason} ->
terminate(Reason, ?STACKTRACE(), Name, undefined, Msg, Mod, State, Debug);
_Msg when Debug =:= [] ->
handle_msg(Msg, Parent, Name, State, Mod, HibernateAfterTimeout);
_Msg ->
Debug1 = sys:handle_debug(Debug, fun print_event/3,
Name, {in, Msg}),
handle_msg(Msg, Parent, Name, State, Mod, HibernateAfterTimeout, Debug1)
end.
2.2 hibernate可以優(yōu)化內(nèi)存
如果可以預(yù)見在短時間內(nèi)沒有消息到達(dá)芝囤,可以讓gen_server休眠(hibernate),可以大大的節(jié)省內(nèi)存開支辛萍,為此我專門寫了一個測試代碼(https://github.com/aijingsun6/erl_hibernate.git),兩種情況都生成10萬個gen_server悯姊,一種情況使用hibernate,另一種情況不使用hibernate,測試結(jié)果如下:
% 使用hibernate
rebar.config
{erl_opts, [
{d, hibernate},
debug_info
]}.
rebar compile
werl -pa ebin -P 300000 -s erl_hibernate
1> erlang:memory().
[{total,209592416},
{processes,170086224},
{processes_used,170085280},
{system,18446744073749057808},
{atom,3699033},
{atom_used,3694166},
{binary,692816},
{code,4682355},
{ets,351424}]
% 不使用hibernate
rebar.config
{erl_opts, [
%{d, hibernate},
debug_info
]}.
rebar compile
werl -pa ebin -P 300000 -s erl_hibernate
1> erlang:memory().
[{total,369103040},
{processes,330034704},
{processes_used,330033760},
{system,39068336},
{atom,3404049},
{atom_used,3393117},
{binary,624272},
{code,4615561},
{ets,350464}]
可見在使用hibernate的情況下贩毕,內(nèi)存大幅減少
3. debug大有用途
trace 可以打印每一條Msg
log 可以捕獲倒數(shù)N條Msg
log_to_file 可以將Msg輸出到文本,特別適用線上追溯問題
statistics 可以統(tǒng)計一段時間的reductions,消息的進(in)出(out)數(shù)量
總結(jié)
正確理解gen_server不僅讓你寫出高效的服務(wù)悯许,而且還會避免不必要的麻煩。