我想在延迟后向进程发送消息,并发现了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
没有做我认为的事情?
你的假设是正确的:这些都是为了做同样的事情,但实现方式不同。
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级别。
也许我们两者都很好,但这绝对应该在文档中明确标出,而且显然不是(我刚检查过)。
如果您有许多计时器,则超时顺序会有所不同。下面的例子显示了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