zoukankan      html  css  js  c++  java
  • [Erlang13]怎么把一个普通的进程挂入Supervisor监控树?

    简单来说:应该是在调用的start_link返回一个{ok,Pid}就可以把这个进程放入监控树Supervisor里面:

       -module(worker).
       -author("zhongwencool@gmail.com").
    
       -export([start_link/0,stop_worker/0]).
    
       start_link() –>
         {ok,spawn(fun() -> loop() end)}.
    
      loop() –>
         case whereis(?MODULE) of
             undefined –>
                 io:format("worker restart~n"),
                 erlang:register(?MODULE,self());
             _ -> ok
         end,
         receive
             stop –>
                 io:format("Worker Stop~n");
             Msg –>
                 io:format("RECV:~w~n",[Msg]),
                 loop()
         end.
    
       stop_worker() –>
          erlang:send(?MODULE,stop).

    这个和gen_server的区别在于,得不到很多如gen_server的行为保证(guarantees),比如gen_sever与supervisor的特性:只有init/1返回后才会返回{ok,Pid},如果你用spawn/1就是异步,无此特性了。

    那么如果,你还是觉得gen_server放在你的需求中有点大材小用,要自己造一个更加轻量的进程,又想拥有类似于gen_server的特性,怎么办呢?

    你可以参照OTP Design Principles: http://www.erlang.org/doc/design_principles/spec_proc.html#id72814

    简单地说就是:使用sys,proce_lib使进程符合:Supervision tree.


     

    6.2  Special Processes

    这部分描述了怎么写一个不使用标准行为(gen_server,gen_fsm,gen_event)模板,写一个完全符合OTP 设计原则的进程要做到那些点:

    1. 这个进程必须能被加入监控树;
    2. 支持 Debug Facilities 
    3. 支持System Messages.

    系统信息(System message )是在监控树里面使用的一种特殊信息,典型特性:支持trace output; 支持suspend or resume process execution(在版本更新时使用);标准的行为包(如gen_server)是自动支持这些特性的。

    例子:

    请看从 Overview 章节拿来来的简单例子:使用sys,proce_lib使进程符合:Supervision tree.
    -module(ch4).
    -export([start_link/0]).
    -export([alloc/0, free/1]).
    -export([init/1]).
    -export([system_continue/3, system_terminate/4,
             write_debug/3,
             system_get_state/1, system_replace_state/2]).
    
    start_link() ->
        proc_lib:start_link(ch4, init, [self()]).
    
    alloc() ->
        ch4 ! {self(), alloc},
        receive
            {ch4, Res} ->
                Res
        end.
    
    free(Ch) ->
        ch4 ! {free, Ch},
        ok.
    
    init(Parent) ->
        register(ch4, self()),
        Chs = channels(),
        Deb = sys:debug_options([]),
        proc_lib:init_ack(Parent, {ok, self()}),
        loop(Chs, Parent, Deb).
    
    loop(Chs, Parent, Deb) ->
        receive
            {From, alloc} ->
                Deb2 = sys:handle_debug(Deb, fun ch4:write_debug/3,
                                        ch4, {in, alloc, From}),
                {Ch, Chs2} = alloc(Chs),
                From ! {ch4, Ch},
                Deb3 = sys:handle_debug(Deb2, fun ch4:write_debug/3,
                                        ch4, {out, {ch4, Ch}, From}),
                loop(Chs2, Parent, Deb3);
            {free, Ch} ->
                Deb2 = sys:handle_debug(Deb, fun ch4:write_debug/3,
                                        ch4, {in, {free, Ch}}),
                Chs2 = free(Ch, Chs),
                loop(Chs2, Parent, Deb2);
    
            {system, From, Request} ->
                sys:handle_system_msg(Request, From, Parent,
                                      ch4, Deb, Chs)
        end.
    
    system_continue(Parent, Deb, Chs) ->
        loop(Chs, Parent, Deb).
    
    system_terminate(Reason, _Parent, _Deb, _Chs) ->
        exit(Reason).
    
    system_get_state(Chs) ->
        {ok, Chs}.
    
    system_replace_state(StateFun, Chs) ->
        NChs = StateFun(Chs),
        {ok, NChs, NChs}.
    
    write_debug(Dev, Event, Name) ->
        io:format(Dev, "~p event = ~p~n", [Name, Event]).
    如何使用以上的sys和ch4模块呢?
    % erl
    Erlang (BEAM) emulator version 5.2.3.6 [hipe] [threads:0]
    
    Eshell V5.2.3.6  (abort with ^G)
    1> ch4:start_link().
    {ok,<0.30.0>}
    2> sys:statistics(ch4, true).
    ok
    3> sys:trace(ch4, true).
    ok
    4> ch4:alloc().
    ch4 event = {in,alloc,<0.25.0>}
    ch4 event = {out,{ch4,ch1},<0.25.0>}
    ch1
    5> ch4:free(ch1).
    ch4 event = {in,{free,ch1}}
    ok
    6> sys:statistics(ch4, get).
    {ok,[{start_time,{{2003,6,13},{9,47,5}}},
         {current_time,{{2003,6,13},{9,47,56}}},
         {reductions,109},
         {messages_in,2},
         {messages_out,1}]}
    7> sys:statistics(ch4, false).
    ok
    8> sys:trace(ch4, false).
    ok
    9> sys:get_status(ch4).
    {status,<0.30.0>,
            {module,ch4},
            [[{'$ancestors',[<0.25.0>]},{'$initial_call',{ch4,init,[<0.25.0>]}}],
             running,<0.25.0>,[],
             [ch1,ch2,ch3]]}

    启动进程要做的事:

    必须使用proc_lib模块的函数来启动进程,这里有几个可能用到的函数,例如:spawn_link/3,4用于异步启动,start_link/3,4,5用于同步启动(和刚才说的要等init/1返回才能再继续一个原理),

       使用proc_lib函数启动的进程会把监控树所要的进程信息(继承关系ancestors,初始华调用initial call等)都保存下来;

        并且,当进程被异常(不是normal or shutdown)终结时,会生成相应的崩溃报告(crash report),你可以查看SASL User’s Guide来得到。

    在上面这个ch4的例子中,使用的是同步启动(和gen_server一样),这个进程启动时调用ch4:start_link():

    start_link() ->
        proc_lib:start_link(ch4, init, [self()]).

    ch4:start_link 调用proc_lib:start_link. 这个函数使用模块明,函数名和一个参数列表来创建(spawns)一个新的进程并links它.新的进程会调用ch4:init(Pid), Pid就是父进程传进来的self().

    在初始华时,所有的初始化(包括注册名字)都会完成,初始化完成时必须要通知父进程已完成:

    init(Parent) ->
        ...
        proc_lib:init_ack(Parent, {ok, self()}),
        loop(...).

    注意:proc_lib:start_link 是同步创建,它会一直等待子进程用 proc_lib:init_ack 返回; 异步创建请用proce_lib:spawn_link.

    Debugging

    我们需要用一个debug 结构(通过sys:debug_options/1初始化得到的term结构)使用sys模块支持Debug 

    init(Parent) ->
        ...
        Deb = sys:debug_options([]),
        ...
        loop(Chs, Parent, Deb).

    sys:debug_options/1 返回一个选择列表(list of options),在这里面是一个空列表,代表没有debugging在初始化时建立,你可以通过sys(3)查看其它可能的选项。

    使用以下函数来记录(logged)或跟踪(traced)每一个我们想要的system event

    sys:handle_debug(Deb, Func, Info, Event) => Deb1
    • Deb 就是在上面被sys:debug_options/1 初始华的debug结构

    • Func 是用户自己定义用于跟踪输出的函数,对于每一个system event,都会调用 Func(Dev, Event, Info),
      • Dev是标准IO设备用于输入,可以查看io(3).

      • EventInfo由 handle_debug得到.
    • Info 可以是任意term 来代表其它附加的信息传给Func.

    • Event 是system event,这取决于用户怎么去定义system event,但是典型至少包括那些进出的消息(incoming and outgoing message),就像这样的结构{in Msg,[,From]} ,{out,Msg,To}.

    handle_debug 返回一个更新的debug structure((Deb1).

    在这个例子中,handle_debug会被所有的进出消息调用,每个消息的处理会调用ch4:write_debug/3 :

    loop(Chs, Parent, Deb) ->
        receive
            {From, alloc} ->
                Deb2 = sys:handle_debug(Deb, fun ch4:write_debug/3,
                                        ch4, {in, alloc, From}),
                {Ch, Chs2} = alloc(Chs),
                From ! {ch4, Ch},
                Deb3 = sys:handle_debug(Deb2, fun ch4:write_debug/3,
                                        ch4, {out, {ch4, Ch}, From}),
                loop(Chs2, Parent, Deb3);
            {free, Ch} ->
                Deb2 = sys:handle_debug(Deb, fun ch4:write_debug/3,
                                        ch4, {in, {free, Ch}}),
                Chs2 = free(Ch, Chs),
                loop(Chs2, Parent, Deb2);
            ...
        end.
    
    write_debug(Dev, Event, Name) ->
        io:format(Dev, "~p event = ~p~n", [Name, Event]).

    Handling System Messages

    收到的系统消息格式如下:

    {system, From, Request}

    这些消息不会打扰到进程,会被自动调用以下的函数处理:

    The content and meaning of these messages do not need to be interpreted by the process. Instead the following function should be called:

    sys:handle_system_msg(Request, From, Parent, Module, Deb, State)

    这个函数不会返回,它会处理完system message 后再调用以下函数来继续这个进程:

    Module:system_continue(Parent, Deb, State)

    也可以调用以下函数来终结这个进程:

    Module:system_terminate(Reason, Parent, Deb, State)

    如果进程应该被终结,那么监控树里的一个监控进程也会以相同的原因终结掉。

    • Request 和From 要从system message 中得到,再用于handle_system_msg;
    • Parent 是父进程的PID;
    • Module 代表的是模块名;
    • Deb 是一个debug结构;
    • State 是用于描述内部state糊状的term,通过system_continue/system_terminate/ system_get_state/system_replace_state来得到。

    如果进程想返回它的类型(类于gen_server:call返回),就使用:

    Module:system_get_state(State)

    或者进程只需要更新StateFunc :

    Module:system_replace_state(StateFun, State)
     
    在上面的例子中:
    loop(Chs, Parent, Deb) ->
        receive
            ...
    
            {system, From, Request} ->
                sys:handle_system_msg(Request, From, Parent,
                                      ch4, Deb, Chs)
        end.
    
    system_continue(Parent, Deb, Chs) ->
        loop(Chs, Parent, Deb).
    
    system_terminate(Reason, Parent, Deb, Chs) ->
        exit(Reason).
    
    system_get_state(Chs) ->
        {ok, Chs, Chs}.
    
    system_replace_state(StateFun, Chs) ->
        NChs = StateFun(Chs),
        {ok, NChs, NChs}.

    如果进程想要设置trap exits,来让进程在终结时调用terminates 那么调用以下会发 {'EXIT', Parent, Reason}处理:

    init(...) ->
        ...,
        process_flag(trap_exit, true),
        ...,
        loop(...).
    
    loop(...) ->
        receive
            ...
    
            {'EXIT', Parent, Reason} ->
                ..maybe some cleaning up here..
                exit(Reason);
            ...
        end.



     
      突然感觉膝盖有些微微的痛。。。
  • 相关阅读:
    联考20200520 T2 函数
    联考20200520 T1 石子游戏
    模拟赛T2 中继系统
    模拟赛T2 仙人掌毒题
    BZOJ3462 DZY Loves Math II
    20200129模拟赛T1 string
    BZOJ1316 树上的询问
    BZOJ4559 成绩比较
    JZOJ4238 纪念碑
    BZOJ 2648 世界树
  • 原文地址:https://www.cnblogs.com/zhongwencool/p/self_superivisor.html
Copyright © 2011-2022 走看看