首页
学习
活动
专区
工具
TVP
发布
精选内容/技术社群/优惠产品,尽在小程序
立即前往

6. sys and proc_lib

sys模块具有对使用行为实现的流程进行简单调试的功能。它还具有与proc_lib模块中的功能一起用于实施符合OTP设计原则而不使用标准行为的特殊过程的功能。这些函数也可以用来实现用户定义的(非标准)行为。

sysproc_lib属于STDLIB应用。

6.1 简单调试

sys模块具有对使用行为实现的流程进行简单调试的功能。以下code_lock示例gen_statem Behaviour用于说明这一点:

代码语言:javascript
复制
Erlang/OTP 20 [DEVELOPMENT] [erts-9.0] [source-5ace45e] [64-bit] [smp:8:8] [ds:8:8:10] [async-threads:10] [hipe] [kernel-poll:false]

Eshell V9.0  (abort with ^G)
1>  code_lock:start_link([1,2,3,4]).
Lock
{ok,<0.63.0>}
2> sys:statistics(code_lock, true).
ok
3>  sys:trace(code_lock, true).
ok
4>  code_lock:button(1).
*DBG* code_lock receive cast {button,1} in state locked
ok
*DBG* code_lock consume cast {button,1} in state locked
5>  code_lock:button(2).
*DBG* code_lock receive cast {button,2} in state locked
ok
*DBG* code_lock consume cast {button,2} in state locked
6>  code_lock:button(3).
*DBG* code_lock receive cast {button,3} in state locked
ok
*DBG* code_lock consume cast {button,3} in state locked
7>  code_lock:button(4).
*DBG* code_lock receive cast {button,4} in state locked
ok
Unlock
*DBG* code_lock consume cast {button,4} in state locked
*DBG* code_lock receive state_timeout lock in state open
Lock
*DBG* code_lock consume state_timeout lock in state open
8> sys:statistics(code_lock, get).
{ok,[{start_time,{{2017,4,21},{16,8,7}}},
     {current_time,{{2017,4,21},{16,9,42}}},
     {reductions,2973},
     {messages_in,5},
     {messages_out,0}]}
9> sys:statistics(code_lock, false).
ok
10> sys:trace(code_lock, false).
ok
11> sys:get_status(code_lock).
{status,<0.63.0>,
        {module,gen_statem},
        [[{'$initial_call',{code_lock,init,1}},
          {'$ancestors',[<0.61.0>]}],
         running,<0.61.0>,[],
         [{header,"Status for state machine code_lock"},
          {data,[{"Status",running},
                 {"Parent",<0.61.0>},
                 {"Logged Events",[]},
                 {"Postponed",[]}]},
          {data,[{"State",
                  {locked,#{code => [1,2,3,4],remaining => [1,2,3,4]}}}]}]]}

6.2 特殊过程

本节介绍如何编写符合OTP设计原则的流程,而不使用标准行为。这样一个过程是:

  • 以一种使过程适合监督树的方式开始
  • 支持 sys debug facilities
  • 照顾system messages

系统消息是监督树中使用的具有特殊含义的消息。典型的系统消息是请求跟踪输出,并请求暂停或恢复流程执行(在释放处理期间使用)。使用标准行为实现的过程自动理解这些消息。

示例

简单的服务器Overview,使用它来实现sysproc_lib因此它适合一个监督树:

代码语言:javascript
复制
-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

代码语言:javascript
复制
% 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同步启动。

使用这些函数之一开始的进程存储监督树中进程所需的信息(例如,关于ancestors和初始调用)。

如果进程与另一个原因,而不是终止normalshutdown,产生崩溃报告。有关崩溃报告的更多信息,请参阅SASL用户指南。

在这个例子中,使用了同步启动。该过程首先调用ch4:start_link()

代码语言:javascript
复制
start_link() ->
    proc_lib:start_link(ch4, init, [self()]).

ch4:start_link调用该函数proc_lib:start_link。该函数将模块名称,函数名称和参数列表作为参数,派生和指向新进程的链接。该新方法通过执行给定的功能启动,这里ch4:init(Pid),其中Pid是PID(self()第一处理,这是父进程的)。

所有初始化,包括名称注册,都是在中完成的init。新流程还必须承认它已经开始对父级:

代码语言:javascript
复制
init(Parent) ->
    ...
    proc_lib:init_ack(Parent, {ok, self()}),
    loop(...).

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

调试

为了支持调试设备sys,需要调试结构。该Deb术语使用sys:debug_options/1以下内容初始化:

代码语言:javascript
复制
init(Parent) ->
    ...
    Deb = sys:debug_options([]),
    ...
    loop(Chs, Parent, Deb).

sys:debug_options/1以选项列表作为参数。这里列表是空的,这意味着最初没有启用调试。有关可能的选项的信息,请参阅sys(3)STDLIB中的手册页。

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

代码语言:javascript
复制
sys:handle_debug(Deb, Func, Info, Event) => Deb1

这里:

  • Deb 是调试结构。
  • Func是一种乐趣,指定用于格式化跟踪输出的(用户定义的)函数。对于每个系统事件,格式函数被调用为Func(Dev, Event, Info),其中:
    • Dev是输出要打印到的I/O设备。请参阅io(3)STDLIB中的手册页。
    • Event并按Info原样通过handle_debug
  • Info用于传递更多信息Func。它可以是任何术语,并按原样传递。
  • Event是系统事件。由用户决定什么是系统事件以及如何表示。通常至少传入和传出的消息被认为是系统事件,{in,Msg[,From]}{out,Msg,To}分别由元组和表示。

handle_debug返回一个更新的调试结构Deb1

在这个例子中,handle_debug每个传入和传出的消息都会被调用。格式化功能Funcch4:write_debug/3使用打印信息的功能io:format/3

代码语言:javascript
复制
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]).

处理系统消息

系统消息被接收为:

代码语言:javascript
复制
{system, From, Request}

这些消息的内容和含义不需要由流程解释。相反,以下函数将被调用:

代码语言:javascript
复制
sys:handle_system_msg(Request, From, Parent, Module, Deb, State)

该函数不返回。它处理系统消息,然后在进程执行继续时调用以下内容:

代码语言:javascript
复制
Module:system_continue(Parent, Deb, State)

或者在进程结束时调用以下内容:

代码语言:javascript
复制
Module:system_terminate(Reason, Parent, Deb, State)

预计监督树中的进程将以与其父进程相同的原因终止。

  • RequestFrom要从系统消息传递到调用handle_system_msg
  • Parent 是父母的pid。
  • Module 是模块的名称。
  • Deb 是调试结构。
  • State是描述内部状态的术语,并传递给system_continue/system_terminate/system_get_state/system_replace_state

如果进程要返回它的状态,handle_system_msg调用:

代码语言:javascript
复制
Module:system_get_state(State)

如果该过程是使用乐趣来替换其状态StateFun,则handle_system_msg调用:

代码语言:javascript
复制
Module:system_replace_state(StateFun, State)

在这个例子中:

代码语言:javascript
复制
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}.

如果特殊进程设置为陷阱退出并且父进程终止,则预期行为将以相同原因终止:

代码语言:javascript
复制
init(...) ->
    ...,
    process_flag(trap_exit, true),
    ...,
    loop(...).

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

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

6.3用户定义的行为

要实现用户定义的行为,请编写类似于特定进程代码的代码,但在回调模块中调用函数以处理特定任务。

如果编译器要警告缺少回调函数(如为OTP行为所做的那样),请-callback在行为模块中添加属性以描述预期的回调函数:

代码语言:javascript
复制
-callback Name1(Arg1_1, Arg1_2, ..., Arg1_N1) -> Res1.
-callback Name2(Arg2_1, Arg2_2, ..., Arg2_N2) -> Res2.
...
-callback NameM(ArgM_1, ArgM_2, ..., ArgM_NM) -> ResM.

NameX是预期回调的名称。ArgX_Y并且ResX是它们在其中描述的类型Types and Function Specifications。该-spec属性的全部语法都由该-callback属性支持。

用于实现行为的用户可选的回调函数通过使用-optional_callbacks属性来指定:

代码语言:javascript
复制
-optional_callbacks([OptName1/OptArity1, ..., OptNameK/OptArityK]).

每个OptName/OptArity指定回调函数的名称和形式。请注意,该-optional_callbacks属性将与该-callback属性一起使用;它不能与behaviour_info()下面描述的功能组合。

需要了解可选回调函数的工具可以调用Behaviour:behaviour_info(optional_callbacks)以获取所有可选回调函数的列表。

注意

我们建议使用-callback属性而不是behaviour_info()函数。原因是额外的类型信息可以被工具用来生成文档或查找差异。

作为一种替代-callback-optional_callbacks属性时,可能直接实现和出口behaviour_info()

代码语言:javascript
复制
behaviour_info(callbacks) ->
    [{Name1, Arity1},...,{NameN, ArityN}].

每个{Name, Arity}指定回调函数的名称和形式。这个函数否则由编译器使用-callback属性自动生成。

当编译器遇到模块-behaviour(Behaviour).中的模块属性时Mod,它会调用Behaviour:behaviour_info(callbacks)结果并将其与实际导出的函数集进行比较Mod,并在缺少回调函数时发出警告。

示例:

代码语言:javascript
复制
%% User-defined behaviour module
-module(simple_server).
-export([start_link/2, init/3, ...]).

-callback init(State :: term()) -> 'ok'.
-callback handle_req(Req :: term(), State :: term()) -> {'ok', Reply :: term()}.
-callback terminate() -> 'ok'.
-callback format_state(State :: term()) -> term().

-optional_callbacks([format_state/1]).

%% Alternatively you may define:
%%
%% -export([behaviour_info/1]).
%% behaviour_info(callbacks) ->
%%     [{init,1},
%%      {handle_req,2},
%%      {terminate,0}].

start_link(Name, Module) ->
    proc_lib:start_link(?MODULE, init, [self(), Name, Module]).

init(Parent, Name, Module) ->
    register(Name, self()),
    ...,
    Dbg = sys:debug_options([]),
    proc_lib:init_ack(Parent, {ok, self()}),
    loop(Parent, Module, Deb, ...).

...

在回调模块中:

代码语言:javascript
复制
-module(db).
-behaviour(simple_server).

-export([init/1, handle_req/2, terminate/0]).

...

-callback通过-spec在回调模块中添加属性,可以进一步细化通过行为模块中的属性指定的合同。这可能很有用,因为-callback合同通常是通用的。具有回调合约的相同回调模块:

代码语言:javascript
复制
-module(db).
-behaviour(simple_server).

-export([init/1, handle_req/2, terminate/0]).

-record(state, {field1 :: [atom()], field2 :: integer()}).

-type state()   :: #state{}.
-type request() :: {'store', term(), term()};
                   {'lookup', term()}.

...

-spec handle_req(request(), state()) -> {'ok', term()}.

...

每份-spec合同都将成为相应-callback合同的子类型。

扫码关注腾讯云开发者

领取腾讯云代金券

http://www.vxiaotou.com