erlang:send_after / 3和timer:send_after / 3是否有不同的行为?

问题描述 投票:5回答:2

我想在延迟后向进程发送消息,并发现了erlang:send_after/4

当看到docs时,看起来这正是我想要的:

erlang:send_after(Time, Dest, Msg, Options) -> TimerRef

启动计时器。当计时器到期时,消息Msg被发送到由Dest标识的进程。

但是,当目标在另一个节点上运行时它似乎不起作用 - 它告诉我其中一个参数是坏的。

1> P = spawn('node@host', module, function, [Arg]).
<10585.83.0>
2> erlang:send_after(1000, P, {123}).
** exception error: bad argument
     in function  erlang:send_after/3
        called as erlang:send_after(1000,<10585.83.0>,{123})

timer:send_after/3做同样的事似乎工作正常:

1> P = spawn('node@host', module, function, [Arg]).
<10101.10.0>
2> timer:send_after(1000, P, {123}).
{ok,{-576458842589535,#Ref<0.1843049418.1937244161.31646>}}

并且,docs状态的timer:send_after/3几乎与erlang版本相同:

send_after(Time, Pid, Message) -> {ok, TRef} | {error, Reason}

评估Pid!时间毫秒后的消息。

所以问题是,为什么这两个函数在表面上做同样的事情时表现不同? erlang:send_after被打破,或错误广告?或者也许timer:send_after没有做我认为的事情?

erlang otp
2个回答
5
投票

TL;DR

你的假设是正确的:这些都是为了做同样的事情,但实现方式不同。

Discussion

timer模块中的东西,例如timer:send_after/2,3,通过gen_server来定义作为服务。像任何其他服务一样,如果您为其分配了大量的任务(计时器),那么这个服务就会过载。

另一方面,erlang:send_after/3,4是作为NIF实施的BIF。如果你有大量的计时器,这绝对是你要走的路。但是,在大多数程序中,您不会注意到差异。

Erlang Efficiency Guide实际上有一个关于这个的说明:

3.1定时器模块

使用erlang:send_after / 3和erlang:start_timer / 3创建定时器比使用STDLIB中定时器模块提供的定时器更有效。计时器模块使用单独的进程来管理计时器。如果许多进程经常创建和取消计时器(特别是在使用SMP仿真器时),那么该进程很容易变得过载。

定时器模块中不管理定时器的功能(如timer:tc / 3或timer:sleep / 1)不会调用定时器服务器进程,因此无害。

解决方法

在没有相同节点限制的情况下获得BIF效率的解决方法是拥有一个自己的进程,它只会等待消息转发到另一个节点:

-module(foo_forward).
-export([send_after/3, cancel/1]).
% Obviously this is an example only. You would want to write this to
% be compliant with proc_lib, write a proper init/N and integrate with
% OTP. Note that this snippet is missing the OTP service functions.

start() ->
    spawn(fun() -> loop(self(), [], none) end).

send_after(Time, Dest, Message) ->
    erlang:send_after(Time, self(), {forward, Dest, Message}).

loop(Parent, Debug, State) ->
    receive
        {forward, Dest, Message} ->
            Dest ! Message,
            loop(Parent, Debug, State);
        {system, From, Request} ->
            sys:handle_msg(Request, From, Parent, ?MODULE, Debug, State);
        Unexpected ->
            ok = log(warning, "Received message: ~tp", [Unexpected]),
            loop(Parent, Debug, State)
    end.

上面的例子有点浅薄,但希望它表达了这一点。应该可以获得BIF erlang:send_after/3,4的效率,但仍然设法跨节点发送消息,并使您可以使用erlang:cancel_timer/1自由取消消息

但为什么?

谜题(和bug)是erlang:send_after/3,4不想跨节点工作的原因。你在上面提供的例子看起来有点奇怪,因为P的第一个任务是Pid <10101.10.0>,但是坠毁的呼叫被报告为<10585.83.0> - 显然不一样。

目前我不知道为什么erlang:send_after/3,4不起作用,但我可以充满信心地说,两者之间的操作机制是不一样的。我会调查它,但我想BIF版本实际上在运行时内做了一些有趣的事情以提高效率,结果通过直接更新其邮箱而不是实际上在更高的Erlang上发送Erlang消息来表示目标进程到Erlang级别。

也许我们两者都很好,但这绝对应该在文档中明确标出,而且显然不是(我刚检查过)。


0
投票

如果您有许多计时器,则超时顺序会有所不同。下面的例子显示了erlang:send_after不保证顺序,但是timer:send_after确实。

1> A = lists:seq(1,10).
[1,2,3,4,5,6,7,8,9,10]
2> [erlang:send_after(100, self(), X) || X <- A].
...
3> flush().
Shell got 2
Shell got 3
Shell got 4
Shell got 5
Shell got 6
Shell got 7
Shell got 8
Shell got 9
Shell got 10
Shell got 1
ok
4> [timer:send_after(100, self(), X) || X <- A]. 
...
5> flush().
Shell got 1
Shell got 2
Shell got 3
Shell got 4
Shell got 5
Shell got 6
Shell got 7
Shell got 8
Shell got 9
Shell got 10
ok
© www.soinside.com 2019 - 2024. All rights reserved.