erlang最常見的組件gen_server

相信用過erlang的同學(xué)對gen_server并不陌生,我們在日常使用中,和gen_server打交道的次數(shù)也是最多的。至于用法我這邊也不會詳細的說明,只是略微帶過,我會將篇幅用在更加不常用但是卻很有用的功能上。

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,
        % 問題出在這里!?。∵@里的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)沒有消息到達,可以讓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ù),而且還會避免不必要的麻煩。

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容