何时在 Erlang 中使用超时和监视器?

use*_*220 4 erlang

《Learn You Some Erlang》一书有以下代码。为什么本书有时只使用超时,有时同时使用监视器和超时?在下面,由于超时将有效地检测进程是否已关闭,因此监视器需要什么?

%% Synchronous call
order_cat(Pid, Name, Color, Description) ->
    Ref = erlang:monitor(process, Pid),
    Pid ! {self(), Ref, {order, Name, Color, Description}},
    receive
        {Ref, Cat} ->
            erlang:demonitor(Ref, [flush]),
            Cat;
        {'DOWN', Ref, process, Pid, Reason} ->
            erlang:error(Reason)
    after 5000 ->
        erlang:error(timeout)
    end.
Run Code Online (Sandbox Code Playgroud)

另请比较以下add_event不使用显示器但subscribe使用显示器的情况

subscribe(Pid) ->
    Ref = erlang:monitor(process, whereis(?MODULE)),
    ?MODULE ! {self(), Ref, {subscribe, Pid}},
    receive
        {Ref, ok} ->
            {ok, Ref};
        {'DOWN', Ref, process, _Pid, Reason} ->
            {error, Reason}
    after 5000 ->
        {error, timeout}
    end.

add_event(Name, Description, TimeOut) ->
    Ref = make_ref(),
    ?MODULE ! {self(), Ref, {add, Name, Description, TimeOut}},
    receive
        {Ref, Msg} -> Msg
    after 5000 ->
        {error, timeout}
    end.
Run Code Online (Sandbox Code Playgroud)

Sea*_*ean 5

这两个例子之间有很大的区别。

order_cat(Pid, Name, Color, Description)在一个请求期间使用监视器,并erlang:demonitor/2在收到成功响应后调用。

subscribe(Pid)建立更永久的监视器。目的是事件客户端将{'DOWN', Ref, process, Pid, Reason}在其主receive块中接收消息并处理事件服务器死亡的事实。请注意如何subscribe(Pid)返回监视器引用以{ok, Ref}供客户端用于此目的。不幸的是,这本书没有展示事件客户端的样子。

现在关于监视器与超时的更普遍的问题:监视的缺点是少量的额外成本和少量的复杂性。与超时相比,优点包括:

  • 如果目标进程不存在或在尝试处理您刚刚发送的消息时崩溃,您会立即发现而不是在超时后发现。对于某些应用程序来说,节省的时间非常重要。
  • 如果目标进程崩溃,Reason监视器消息的一部分会告诉您原因。在某些应用中,客户端可能会根据原因尝试不同的恢复操作。
  • 发送进程知道将来是否期望目标进程的响应。在超时的情况下,如果目标进程只是响应缓慢,则发送进程将在其邮箱中收到响应。如果发件人未能从其邮箱中清除此类响应,它将变得缓慢并最终终止。本书在选择性接收部分谈到了这个问题。该gen_server:call/3文档还简要解释了这个问题。

gen_server:call/2 的实现使用监视器。由于这是发送的生产 erlang 请求数,因此您可以相信它经过了很好的优化,并且是推荐的默认值。事实上,您应该使用 OTP,而不是自己动手。

请参阅此处的源代码。这是相关的功能:

do_call(进程、标签、请求、超时) ->
    尝试 erlang:monitor(process, Process) 的
    参考文献->
        %% 如果monitor/2调用未能建立到a的连接
        %% 远程节点,我们不需要 '!' 操作员尝试
        %% 重新建立连接。(如果监视器/2调用
        %% 由于超时而失败,'!' 也可能会
        %%必须等待超时到期。)因此,
        %% 使用 erlang:send/3 和 'noconnect' 选项,这样它
        如果没有连接到%%将立即失败
        %% 远程节点。

        catch erlang:send(Process, {Label, {self(), Mref}, Request},
          [无连接]),
        收到
        {Mref,回复} ->
            erlang:demonitor(Mref, [flush]),
            {好的,回复};
        {'DOWN', Mref, _, _, noconnection} ->
            节点 = get_node(进程),
            退出({nodedown,节点});
        {'向下', Mref, _, _, 原因} ->
            退出(原因)
        超时后->
            erlang:demonitor(Mref, [flush]),
            退出(超时)
        结尾
    抓住
    错误:_ ->
        %% 节点(C/Java?)不支持监视器。
        %% 另一种可能的情况——该节点不是分布式的
        %%——应该早点处理。
        %% 使用monitor_node/2 做到最好。
        %% 如果进程
        %% 不存在。它仅用于功能较弱的远程节点。
        节点 = get_node(进程),
        Monitor_node(节点,真),
        收到
        {nodedown, 节点} ->
            Monitor_node(节点,假),
            退出({nodedown,节点})
        0 之后 ->
            标签 = make_ref(),
            过程 !{标签,{self(),标签},请求},
            wait_resp(节点、标签、超时)
        结尾
    结尾。