生成和接收的 erlang 竞争条件

erlang race condition of spawn and receive

我正在通过书本学习 erlang,在第 13 章中,第一个练习是编写一个函数 my_spawn,它在生成消息时捕获退出消息 crash/exited。

-module(my_spawn1).
-compile(export_all).

my_spawn(Mod,Func,Args) ->
    {M1, S1, Mi1} = os:timestamp(),
    Pid = spawn(Mod,Func,Args),
    lib_misc:on_exit(Pid, fun(Why) ->
                  {M2,S2,Mi2} = os:timestamp(),
                  ElapsedTime = (M2 - M1) * 1000000 + (S2 - S1) * 1000 + (Mi2-Mi1),
                  io:format("~p died with:~p~n consume time:~p(ms)", [Pid,Why,ElapsedTime]),
              end),
    Pid.

我的困惑是,如果在 spawn_monitor 之后,Mod:Func(Args) 完成了,但是 lib_misc:on_exit(...) 还没有设置,那么退出消息就会丢失,真的吗?

如果那是正确的,那么如何捕捉这种情况?

[Pascal 编辑]

我为 lib_misc 添加代码:on_exit/2

on_exit(Pid, Fun) ->
    spawn(fun() -> 
          process_flag(trap_exit, true), %% <label id="code.onexit1"/>
          link(Pid),                     %% <label id="code.onexit2"/>
          receive
              {'EXIT', Pid, Why} ->      %% <label id="code.onexit3"/>
              Fun(Why)   %% <label id="code.onexit4"/>
          end
      end).

不,该消息(与所有消息一样)将被排队。但是,如果接收消息的进程终止,则其邮箱将丢失。

如果 A spawn_monitor 生成 B,B 立即死亡,A 肯定会收到 DOWN 消息。但是,如果 A 也死了,那么 A 的消息队列中的所有内容都将丢失。

函数 on_exit 做的第一件事是生成一个进程,将 process_flag trap_exit 设置为 true,因此它 "protected" 不会崩溃,并且会改为接收以下类型的消息:{'EXIT', Pid, Why}.

在下一行它试图link自己到Pid;可能有 2 种情况:

  • Pid 不存在(错误值,已经死了...)然后 on_exit 进程将收到消息 {'EXIT', Pid, noproc} 并将调用 F(noproc)。
  • Pid 存在,然后 on_exit 进程将等待接收,直到进程 Pid 因某种原因死亡。 on_exit 将收到 {'EXIT', Pid, Reason} 并调用 F(Reason)。

我不明白你为什么说spawn_monitor,它没有用在你的情况下。无论如何,如果您在 on_exit 函数中将 link(Pid) 替换为 monitor(process,Pid) ,您甚至不需要使用 trap_exit 因为 monitor 函数不会崩溃如果 Pid 死了。在所有情况下,它 returns 消息 {'DOWN',MonitorReference,process,Pid,Reason}。 on_exit 可以这样修改:

on_exit(Pid, Fun) ->
    spawn(fun() -> 
          MonitorReference = monitor(process,Pid),
          receive
              {'DOWN',MonitorReference,process,Pid,Why} -> Fun(Why)
          end
      end).