您的代码不会崩溃,因为所有进程都是本地的。
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其中enqueues邮箱中的消息。在 SMP 模式下,线程实际上获取了邮箱的锁。
因此,当评估第 4 行并将 A 发送到进程 B 时,A 的邮箱中已经有 m1。所以m2只能在m1之后入队。
这个结果是否特定于 Erlang 的当前实现是有争议的,即使文档不能保证这一点。事实上,每个进程都需要一个邮箱,并且这个邮箱需要以某种方式填充。这是在第 3 行同步完成的。要异步完成,要么需要另一个线程,要么每个进程需要多个邮箱(例如,每个调度程序一个,以避免锁定邮箱)。然而,我认为这在性能方面没有意义。
如果进程 A 和 B 是远程的但在同一个节点中,则行为略有不同,但结果与当前的 Erlang 实现相同。在第 3 行,消息m1 将为远程节点排队,在第 4 行,消息A 将随后排队。远程节点出列消息时,会先将m1写入A的邮箱,然后再将A写入B的邮箱。
如果进程A 是远程的,B 是本地的,结果还是一样的。在第 3 行,消息 m1 将被排队等待远程节点,在第 4 行,消息将被写入 B,但是在第 1 行,消息 m2 将在m1 之后排队到远程节点。所以A会按照m1、m2的顺序获取消息。
同样,如果进程 A 是本地的而 B 是远程的,则 A 将在第 3 行将消息复制到其邮箱,然后再通过网络将任何内容发送到 B 的节点。
在当前版本的 Erlang 中,崩溃的唯一方法是使 A 和 B 在不同的远程节点上。在这种情况下,m1 在 A 排队到 B 的节点之前被排队到 A 的节点。但是,这些消息的传递不是同步的。例如,如果许多消息已经为 A 的节点排队。
以下代码(有时)通过向 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}.