您的代码不会崩溃,因为所有进程都是本地的。
B = spawn_link(fun() -> receive P -> P ! m2 end end), % 1
A = spawn_link(fun() -> receive X -> X=m1 end end), % 2
A ! m1, % 3
B ! A. % 4
在评估第 3 行时,BEAM 仿真器和 HiPE 都会调用erl_send内置函数 (BIF)。由于 A 是一个本地进程, erl_send (实际上是do_send)最终会调用erts_send_message,它会将邮箱中的消息排入队列。在 SMP 模式下,线程实际上获取了邮箱上的锁。
因此,当评估第 4 行并将 A 发送到进程 B 时,A 的邮箱中已经有 m1。所以m2
只能入队后m1
。
这个结果是否特定于 Erlang 的当前实现是有争议的,即使文档不能保证这一点。事实上,每个进程都需要一个邮箱,并且这个邮箱需要以某种方式填充。这是在第 3 行同步完成的。要异步完成,要么需要另一个线程,要么每个进程需要多个邮箱(例如,每个调度程序一个,以避免邮箱锁定)。然而,我认为这在性能方面没有意义。
如果进程A 和 B 是远程的但在同一个节点中,则行为略有不同,但结果与 Erlang 的当前实现相同。在第 3 行,m1
远程节点的消息将入队,在第 4 行,消息A
将随后入队。当远程节点将消息出列时,它会先写入m1
A 的邮箱,然后再写入A
B 的邮箱。
如果进程A 是 remote 而 B 是 local,结果仍然是相同的。在第 3 行,消息m1
将被排队到远程节点,在第 4 行,消息将被写入 B,但是在第 1 行,消息m2
将在之后排队到远程节点m1
。所以 A 将以 m1, m2 的顺序获取消息。
同样,如果进程A 是本地进程 B 是远程进程,则A 将在第 3 行将消息复制到其邮箱,然后再通过网络将任何内容发送到 B 的节点。
对于当前版本的 Erlang,崩溃的唯一方法是将A 和 B 放在不同的远程节点上。在这种情况下,m1
先入队到 A 的节点,然后A
再入队到 B 的节点。但是,这些消息的传递不是同步的。例如,如果许多消息已经为 A 的节点排队,则可能首先发送到 B 的节点。
以下代码(有时)通过将队列填充到 A 节点的垃圾消息来触发崩溃,这些垃圾消息会减慢m1
.
$ erl -sname node_c@localhost
C = spawn_link(fun() ->
A = receive {process_a, APid} -> APid end,
B = receive {process_b, BPid} -> BPid end,
ANode = node(A),
lists:foreach(fun(_) ->
rpc:cast(ANode, erlang, whereis, [user])
end, lists:seq(1, 10000)),
A ! m1,
B ! A
end),
register(process_c, C).
$ erl -sname node_b@localhost
B = spawn_link(fun() -> receive P -> P ! m2 end end),
C = rpc:call(node_c@localhost, erlang, whereis, [process_c]),
C ! {process_b, B}.
$ erl -sname node_a@localhost
A = spawn_link(fun() -> receive X -> X = m1 end, io:format("end of A\n") end),
C = rpc:call(node_c@localhost, erlang, whereis, [process_c]),
C ! {process_a, A}.