在分析完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 的调用者返回结果

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

所以,尽可能快的返回,不在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:

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

Instead, consider rewriting it as:

 init(Args) ->
Opts = parse_args(Args),
%% you could try connecting here anyway, for a best
%% effort thing, but be ready to not have a connection.
self() ! reconnect, %% 给self 发送消息,替代在init 时connect的耗时/阻塞操作
{ok, #state{sock=undefined, opts=Opts}}.
[...]
handle_info(reconnect, S = #state{sock=undefined, opts=Opts}) ->
%% try reconnecting in a loop
case connect(Opts) of
{ok, New} -> {noreply, S#state{sock=New}};
_ -> self() ! reconnect, {noreply, S}
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 函数进行处理.

 %%% ---------------------------------------------------
%%% The MAIN loop.
%%% ---------------------------------------------------
loop(Parent, Name, State, Mod, hibernate, Debug) ->
%% 这个坑在说hibernate 的时候再填
proc_lib:hibernate(?MODULE,wake_hib,[Parent, Name, State, Mod, Debug]);
loop(Parent, Name, State, Mod, Time, Debug) ->
Msg = receive
  Input ->
    Input
   after Time ->
     timeout
   end,
decode_msg(Msg, Parent, Name, State, Mod, Time, Debug, false). wake_hib(Parent, Name, State, Mod, Debug) ->
Msg = receive
Input ->
  Input
end,
decode_msg(Msg, Parent, Name, State, Mod, hibernate, Debug, true). decode_msg(Msg, Parent, Name, State, Mod, Time, Debug, Hib) ->
case Msg of
%% 这个坑在说sys trace/get_status 的时候填
  {system, From, Req} ->
  sys:handle_system_msg(Req, From, Parent, ?MODULE, Debug,
  [Name, State, Mod, Time], Hib);
%% 这个坑在说 terminate 的时候填
  {'EXIT', Parent, Reason} ->
  terminate(Reason, Name, Msg, Mod, State, Debug);
  _Msg when Debug =:= [] ->
  handle_msg(Msg, Parent, Name, State, Mod);
  _Msg ->
  Debug1 = sys:handle_debug(Debug, fun print_event/3,
  Name, {in, Msg}),
  handle_msg(Msg, Parent, Name, State, Mod, Debug1)
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 表的信息. 如:

 create(Name) ->
_ = ensure_started(),
case ets:member(pg2_table, {group, Name}) of
false ->
global:trans({{?MODULE, Name}, self()},
fun() ->
gen_server:multi_call(?MODULE, {create, Name})
end),
ok;
true ->
ok
end.

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

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

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

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

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

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

  1. Erlang generic standard behaviours -- gen_server system msg

    这是Erlang generic standard behaviors gen_server 分析的系列的最后一篇,主要分析gen_server module 辅助性的功能函数. 在gen_serve ...

  2. Erlang generic standard behaviours -- gen_server hibernate

    hibernate 主要用于在内存空闲时,通过整理进程的stack,回收进程的heap 来达到回收内存节省资源的效果. hibernate 可用于OTP 进程以及普通进程, hibernate 的官方 ...

  3. Erlang generic standard behaviours -- gen_server terminate

    gen_server 主体 module 已经分析完了(http://www.cnblogs.com/--00/p/4271982.html),接着,分析下gen_server 中的terminate ...

  4. Erlang generic standard behaviours -- gen_server noblock call

    在Erlang 系统中,经常需要gen_server 进程来处理共享性的数据,也就是总希望一个gen_server 进程来为多个普通进程提供某种通用性的服务,这也是gen_server 设计的初衷.但 ...

  5. Erlang generic standard behaviours -- gen

    在分析 gen_server (或者是gen_fsm )之前,首先应该弄明白,gen 这个module . -module(gen). -compile({inline,[get_node/1]}). ...

  6. Erlang generic standard behaviours -- summary

    gen_server 相关的片段分析得也差不多了, 这篇作为一个简要的总结.这一系列相关的分析暂且告一段落(之后如有必要,还会回来的 ^^ ),下一个系列主要是以pool 相关, 包括但不仅限于开源项 ...

  7. erlang四大behaviour之一gen_server

      来源:http://www.cnblogs.com/puputu/articles/1701017.html erlang程序设计里面有个设计原则就是把你的进程构造成树,把共用代码提出来,特定功能 ...

  8. erlang四大behaviour之一gen_server(转载)

    erlang程序设计里面有个设计原则就是把你的进程构造成树,把共用代码提出来,特定功能用自己的module实现,这也就是behaviour了,应用behaviour可以减少与本身事务无关的代码量,设计 ...

  9. Erlang模块gen_server翻译

    gen_server 概要: 通用服务器行为描述: 行为模块实现服务器的客户端-服务器关系.一个通用的服务器进程使用这个模块将实现一组标准的接口功能,包括跟踪和错误报告功能.它也符合OTP进程监控树. ...

随机推荐

  1. 学习总结 java Iterator迭代器练习

    package com.hanqi.jh; import java.util.*; public class Text3 { public static void main(String[] args ...

  2. vc不用IDE编译方法

    一个EXE是如何形成的 比如一个源程序有以下两个文件. 1.c 1.rc 首先cl.exe 会把源代码编译为1.obj rc.exe会把1.rc编译为1.res link.exe会把1.obj 1.r ...

  3. Retina CS强大漏洞检测工具

    RetinaCS强大漏洞检测工具 Eeye数字安全公司成立于上世纪九十年代末期,它是世界领先的安全公司,它采用最新研究成果和创新技术来保证您的网络兄系统安全,并向您提供最强大的如下服务:全面的.漏洞评 ...

  4. sql 截取字符串第一次出现字符之前的数据

    截取sql 第一次出现字符之前的数据  (select left( a.ChangeProductName,charindex(',', ChangeProductName)-1)) as Chang ...

  5. Heavily reliance on forensic tools is risky

    We could take advantage of forensic tools to examine and analyze the evidence, but heavily reliance ...

  6. AnyCAD三维控件场景节点详解

    SceneNode是AnyCAD三维图形平台的AnyViz显示引擎的核心对象之一,只有放在场景管理器(SceneManager)里的节点才能被显示引擎所显示. 1.   节点分类 SceneNode是 ...

  7. jquery.form.js实现将form提交转为ajax方式提交的使用方法

    本文实例讲述了jquery.form.js实现将form提交转为ajax方式提交的方法.分享给大家供大家参考.具体分析如下: 这个框架集合form提交.验证.上传的功能. 这个框架必须和jquery完 ...

  8. DBLINK 创建与小结

    1.DBLINK 的作用 当用户要跨本地数据库,访问另外一个数据库表中的数据时,本地数据库中必须创建了远程数据库的dblink,通过dblink本地数据库可以像访问本地数据库一样访问远程数据库表中的数 ...

  9. DataGridView控件中添加ComboBox下拉列表框的实现

    //ComboBox控件拖放到DataGridView控件的某个位置 //添加年龄下拉框 private void BindAge() { //我这里添加的是静态数据,一般都是从数据库读出来的,这里就 ...

  10. luigi学习1

    一.luigi介绍 luigi是基于python语言的,可帮助建立复杂流式批处理任务管理系统.这些批处理作业典型的有hadoop job,数据库数据的导入与导出,或者是机器学习算法等等. luigi的 ...