启动进程

启动进程

要使用 proc_lib 模块中的某一个函数来启动进程。有好几个函数可以用,例如异步启动的 spawn_link/3,4 以及同步启动的 start_link/3,4,5 。

使用这些函数中的任何一个启动的进程都会储存监督树所必须的信息,例如祖先和初始化调用的信息。

另外,如果进程终止的原因不是 normal 或者 shutdown ,那么会生成一个崩溃报告(见SASL用户指南)。

在上面的例子中,使用的是同步启动。进程通过调用 ch4:start_link() 来启动:

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

ch4:start_link 调用了函数 proc_lib:start_link 。这个函数接受一个模块名称、一个函数名称和一个参数列表作为它的参数,并生成、联接到新的进程。新的进程通过执行指定的函数启动,在这个例子中是 ch4:init(Pid) ,其中 Pid 是第一个进程的pid( self() ),也就是父进程。

在 init ,要完成所有的初始化,包括名称的注册。新的进程还必须向父进程应答它已经被启动了:

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

proc_lib:start_link 是同步调用,直到 proc_lib:init_ack 被调用后才返回。

调试

要支持 sys 中的调试设备,我们需要一个调试结构 ,即使用 sys:debug_option/1 初始化的一个值 Deb :

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

sys:debug option/1 接受一个选项列表作为参数。在这里,这个列表为空,表示初始的时候没有启动任何调试。可用的选项的信息参见 _sys(3) 。

然后对于每一个我们要记录或跟踪的系统事件 ,都需要调用以下函数。

sys:handle_debug(Deb, Func, Info, Event) => Deb1
  • Deb 是调试结构。
  • Func 是一个元组 {模块, 函数名}({Module,Name}),或一个fun,必须指定一个用于格式化跟踪输出的(用户定义的)函数。对每一个系统事件,格式化函数的调用形式为 Module:Name(Dev,Event,Info) ,其中:
    • Dev 是输出所打印的IO设备。参见 io(3) 。
    • Event 和 Info 是原样从 handle_debug 传递过来的。
  • Info 用于传递额外的信息给 Func ,它可以是任何值,会被原样传递过去。
  • Event 是系统事件。如何定义一个系统事件以及它应该如何表示,都是由用户来定义的,不过一般至少进来和出去的消息被认为是系统消息,相应由元组 {in,Msg[,From]} 和 {out,Msg,To} 来表示。handle_debug 返回一个更新了的调试结构 Deb1 。

在上面的例子, 每一个进来和出去的消息都调用了 handle_debug 。格式化函数 Func 是 ch4:write_debug/3 使用 io:format:/3 打印消息。

loop(Chs, Parent, Deb) ->
    receive
        {From, alloc} ->
            Deb2 = sys:handle_debug(Deb, {ch4, write_debug},
                                    ch4, {in, alloc, From}),
            {Ch, Chs2} = alloc(Chs),
            From ! {ch4, Ch},
            Deb3 = sys:handle_debug(Deb2, {ch4, write_debug},
                                    ch4, {out, {ch4, Ch}, From}),
            loop(Chs2, Parent, Deb3);
        {free, Ch} ->
            Deb2 = sys:handle_debug(Deb, {ch4, write_debug},
                                    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]).

处理系统消息

接收到的 系统消息 形如:

{system, From, Request}

这些消息的内容和含义无须由进程来解释,应交由以下函数:

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

这个函数不会返回。它会处理系统消息然后,如果需要进程继续执行则调用:

Module:system_continue(Parent, Deb, State)

如果进程要终止则执行:

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

注意监督树中的进程要用和父进程一样的理由终止。

  • Request 和 From 必须原样从系统消息传递给 handle_system_msg 调用。
  • Parent 是父进程的pid。
  • Module 是模块的名字。
  • Deb 是调试结构。
  • State 是描述初始状态的值并且会被传递给 system_continue/system_terminate 。在前面的例子中:
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).

如果特殊进程被被设置为捕获退出,那么要注意如果父进程终止了,它也要以同样的理由终止。

init(...) ->
    ...,
    process_flag(trap_exit, true),
    ...,
    loop(...).

loop(...) ->
    receive
        ...

        {'EXIT', Parent, Reason} ->
            ..maybe some cleaning up here..
            exit(Reason);
        ...
    end.