Erlang generic standard behaviours -- gen_server module

在分析完gen module 之后,就可以开始进入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 失败的流程进行处理.

时间: 2024-08-28 17:01:50

Erlang generic standard behaviours -- gen_server module的相关文章

Erlang generic standard behaviours -- gen_server system msg

这是Erlang generic standard behaviors gen_server 分析的系列的最后一篇,主要分析gen_server module 辅助性的功能函数. 在gen_server 的MAIN loop 流程中,除了处理Parent 的'EXIT' 消息, user module 常规消息之外, 还处理了一类 system 消息. 这一类system 消息的来源是一个sys 的module,在Erlang OTP体系中,sys module 主要有两大类的作用,一个是热更,

Erlang generic standard behaviours -- gen_server terminate

gen_server 主体 module 已经分析完了(http://www.cnblogs.com/--00/p/4271982.html),接着,分析下gen_server 中的terminate .首先分析一个问题, 这个问题是之前在weibo 上和别人讨论过的一个问题: Why will a rpc:call started gen_server process terminate with normal reason? 注:被call 的gen_server 进程 trap_exit

Erlang generic standard behaviours -- gen_server noblock call

在Erlang 系统中,经常需要gen_server 进程来处理共享性的数据,也就是总希望一个gen_server 进程来为多个普通进程提供某种通用性的服务,这也是gen_server 设计的初衷.但是,由于公平调度的原因,在Erlang体系中,每个process 能获得的资源都是同等的:同等的CPU时间片(还有默认情况下同等的初始化内存). 也就是gen_server 进程只能获得1/(N+1)的CPU时间片,为N个进程提供通用性的服务,而无法违背公平调度的原则使gen_server 进程获得

Erlang generic standard behaviours -- gen

在分析 gen_server (或者是gen_fsm )之前,首先应该弄明白,gen 这个module . 1 -module(gen). 2 -compile({inline,[get_node/1]}). 3 4 %%%----------------------------------------------------------------- 5 %%% This module implements the really generic stuff of the generic 6 %

[Erlang之旅 0004] gen_server

gen_server:start_link(ServerName, Module, Args, Options) -> Result ServerName={local, Name}|{global, GlobalName}|{via, Module, ViaName} Option = {debug, Dbgs}|{timeout, Time}|{spawn ——opt, Sopts} Module: 回调模块的名称 Args: 传给回调模块中init函数的参数 call(ServerRef,

erlang中启动一个gen_server和一个纯粹进程的区别

1.在genserver的启动过程中,内部调用的是proc_lib:start_link(M,F,A,Time,SpawnOpts),当该函数被调用的时候,启动gen_server的进程必须wait,直到gen_server进程在执行init_it函数时,调用proc_lib:init_ack将gen_server的进程号返回给启动进程,至此一个gen_server进程才启动完成: 2.对于一个单纯的erlang进程来说,调用spawn函数来启动他,会立刻返回.

开博 -- Erlang

接触Erlang有段时间了,之前一直懒得总结,发现很多东西都是散落各处,总想拾起,可又感觉像是流沙. 就从现在开始吧,慢慢总结起来,捡起来. 先计划写一些系列性的,比如说generic standard behaviours.beach的一些小东西.常用的一些开源工具.生产环境下虚拟机参数的调整.看到的一些论文.重复造的一些轮子等,也希望能更进一步,看一些erts 的内容,尝试学习.分析. 如果有幸能被一些博友看到,也希望能多交流. :)

Erlang OTP学习(1)gen_server

在<Programming Erlang>的OTP introduction章节中,作者通过循序渐进的方式,向我们展示了gen_server设计思路,现在做下总结: 在具体看gen_server之前,我们先看一个server通用框架:  在这个server里,你几乎看不到任何和具体功能相关的东西,它只提供了一个server所具备的基本框架,那它是如何运行的呢? 当我们调用start函数时,就启动了一个服务,如果服务器接收到一条消息,那么它将会把这条消息转交给Mod:handle_call或者M

Gen_server行为分析与实践

1.简介 Gen_server实现了通用服务器client_server原理,几个不同的客户端去分享服务端管理的资源(如图),gen_server提供标准的接口函数和包含追踪功能以及错误报告来实现通用的服务器,同时可以作为OTP监控树的一部分. Gen_server函数与回调函数之间的关系: 1 gen_server module Callback module 2 ----------------- --------------- 3 gen_server:start_link ----->