zoukankan      html  css  js  c++  java
  • Erlang generic standard behaviours -- gen_server module

    在分析完gen module (http://www.cnblogs.com/--00/p/4271386.html)之后,就可以开始进入gen_server 的主体module 了.gen_server 的主体 module 暂不涵括terminate, hibernate, debug trace 相关的内容,这些会单独拉出来分析.

    gen_server 主要包括start 初始化部分, MAIN loop. 其中MAIN loop 为gen_server的主体结构, 其中,处理Label 为'$gen_call' (也就是handle_call)的消息使用handle_msg 处理, Label为'$gen_cast'(也就是handle_cast)的消息以及无Label(handle_info)的消息经由handle_msg最终交给try_dispatch 函数.

    gen_server start

    gen_server start 是由外部进程调用gen_server module 的start/start_link 函数,用以创建新的gen_server behavior 的进程. 在标准的Erlang application 中,一般是由supervisor 角色发起. start 的流程见下图

    如图中所示, ProcessA 是gen_server start 的调用者, 通过user module 的start/start_link 函数, 调用gen_server module 中的start, 继而调用gen module 中的start. 在gen module中, 由proc_lib:spawn 创建新的进程(ProcessB), 并以此调用init_it(gen); init_it(gen_server) ; init(user module) 完成gen_server behavior 进程的初始化.

    都成说,gen_server behavior 的user module init 函数尽可能快的返回, 不要做任何阻塞性的操作.

    在gen_server 的init_it 函数中, Mod:init 返回之后, 会调用proc_lib:init_ack/2, 用于向 start 的调用者返回结果

     1 init_it(Starter, self, Name, Mod, Args, Options) ->
     2     init_it(Starter, self(), Name, Mod, Args, Options); %% 注意, 如果使用nolink start 时, Parent 就是自己
     3 init_it(Starter, Parent, Name0, Mod, Args, Options) ->
     4     Name = name(Name0),
     5     Debug = debug_options(Name, Options),
     6     case catch Mod:init(Args) of
     7     {ok, State} ->
     8         proc_lib:init_ack(Starter, {ok, self()}),    %% 向调用者返回结果    
     9         loop(Parent, Name, State, Mod, infinity, Debug);
    10     {ok, State, Timeout} ->
    11         proc_lib:init_ack(Starter, {ok, self()}),         
    12         loop(Parent, Name, State, Mod, Timeout, Debug);
    13         …………

    所以,尽可能快的返回,不在Mod:init中做任何阻塞以及耗时性的操作.

    但是,很多情况下,在Mod:init 处理过程中,是用于与外部资源(如:DB,MQ等)创建链接,而这些操作很难确定其耗时性,咋办?牛逼的Erlang大神 Ferd 在其 Erlang in Anger (国内有翻译版:硝烟中的Erlang——Erlang 生产系统问题诊断、调试、解决指南) 中提供了一种方式

    The following code attempts to guarantee a connection as part of the process’ state: 

     1 init(Args) ->
     2     Opts = parse_args(Args),
     3     {ok, Port} = connect(Opts),   %% 这种在init 函数中执行connect的方式不可取        
     4     {ok, #state{sock=Port, opts=Opts}}.
     5 [...]
     6 handle_info(reconnect, S = #state{sock=undefined, opts=Opts}) ->
     7     %% try reconnecting in a loop
     8     case connect(Opts) of
     9         {ok, New} -> {noreply, S#state{sock=New}};
    10          _ -> self() ! reconnect, {noreply, S}
    11 end;

    Instead, consider rewriting it as: 

     1 init(Args) ->
     2     Opts = parse_args(Args),
     3     %% you could try connecting here anyway, for a best
     4     %% effort thing, but be ready to not have a connection.
     5     self() ! reconnect,   %% 给self 发送消息,替代在init 时connect的耗时/阻塞操作
     6     {ok, #state{sock=undefined, opts=Opts}}.
     7 [...]
     8 handle_info(reconnect, S = #state{sock=undefined, opts=Opts}) ->
     9     %% try reconnecting in a loop
    10     case connect(Opts) of
    11         {ok, New} -> {noreply, S#state{sock=New}};
    12         _ -> self() ! reconnect, {noreply, S}
    13     end;

    注意: 第一种方式不可取.

    gen_server MAIN loop

    使用proc_lib:init_ack 之后, gen_server init_it 会调用loop 进入gen_server 的MAIN loop 流程中. MAIN loop 使用receive 用以接收'$gen_call', '$gen_cast' 以及其他的message, 紧接着交由 decode_msg 函数进行处理.

     1 %%% ---------------------------------------------------
     2 %%% The MAIN loop.
     3 %%% ---------------------------------------------------
     4 loop(Parent, Name, State, Mod, hibernate, Debug) ->
     5     %% 这个坑在说hibernate 的时候再填
     6     proc_lib:hibernate(?MODULE,wake_hib,[Parent, Name, State, Mod, Debug]);
     7 loop(Parent, Name, State, Mod, Time, Debug) ->
     8     Msg = receive
     9            Input ->
    10                 Input
    11          after Time ->
    12                timeout
    13          end,
    14     decode_msg(Msg, Parent, Name, State, Mod, Time, Debug, false).
    15 
    16 wake_hib(Parent, Name, State, Mod, Debug) ->
    17     Msg = receive
    18           Input ->
    19             Input
    20       end,
    21     decode_msg(Msg, Parent, Name, State, Mod, hibernate, Debug, true).
    22 
    23 decode_msg(Msg, Parent, Name, State, Mod, Time, Debug, Hib) ->
    24     case Msg of
    25         %% 这个坑在说sys trace/get_status 的时候填
    26       {system, From, Req} ->
    27           sys:handle_system_msg(Req, From, Parent, ?MODULE, Debug,
    28                     [Name, State, Mod, Time], Hib);
    29     %% 这个坑在说 terminate 的时候填
    30       {'EXIT', Parent, Reason} ->
    31           terminate(Reason, Name, Msg, Mod, State, Debug);
    32       _Msg when Debug =:= [] ->
    33           handle_msg(Msg, Parent, Name, State, Mod);
    34       _Msg ->
    35           Debug1 = sys:handle_debug(Debug, fun print_event/3,
    36                         Name, {in, Msg}),
    37           handle_msg(Msg, Parent, Name, State, Mod, Debug1)
    38     end.

    在上面的代码片段中, L8 正是receive self 或外部进程的message, L23 是decode_msg 函数的入口, 在L33和L37 处调用handle_msg 函数进一步对msg消息进行处理.代码比较简单,没必要一行一行分析了.

     gen_server multi_call

    call 在上一篇blog中已经提到, cast以及abcast 的实质就是调用erlang:send bif, 最终调用erts beam 下的dist.c .

    multi_call 牵扯到多node , Erlang stdlib 中pg2 module 就主要使用multi_call 同步各自node 上ets 表的信息. 如:

     1 create(Name) ->
     2     _ = ensure_started(),
     3     case ets:member(pg2_table, {group, Name}) of
     4         false ->
     5             global:trans({{?MODULE, Name}, self()},
     6                          fun() ->
     7                                  gen_server:multi_call(?MODULE, {create, Name})
     8                          end),
     9             ok;
    10         true ->
    11             ok
    12     end.

    multi_call 调用 do_multi_call 函数, do_multi_call 使用Middleman process . Middleman process 负责给各node 发送 Label 为 '$gen_call' 的消息并等待各node 的结果返回. 

    1 %% Middleman process. Should be unsensitive to regular
    2 %% exit signals. The sychronization is needed in case
    3 %% the receiver would exit before the caller started
    4 %% the monitor.

    最终, 通过exit 的方式返回给主调用进程, 而主调用进程会通过monitor/receive {'DOWN' ...} 的方式接收结果.

    注意: Middleman process 需要monitor 目标node, 如果nodedown, 即会采取 call 失败的流程进行处理. 

    参考: http://www.erlang-in-anger.com/

  • 相关阅读:
    Linux内核RPC请求过程
    二分图
    Java实现 蓝桥杯 算法提高 合并石子
    Java实现 蓝桥杯 算法提高 合并石子
    Java实现 蓝桥杯 算法提高 摩尔斯电码
    Java实现 蓝桥杯 算法提高 摩尔斯电码
    Java实现 蓝桥杯 算法提高 文本加密
    Java实现 蓝桥杯 算法提高 文本加密
    Java蓝桥杯 算法提高 九宫格
    Java蓝桥杯 算法提高 九宫格
  • 原文地址:https://www.cnblogs.com/--00/p/4271982.html
Copyright © 2011-2022 走看看