简单代码:
-module(on_exit).
-export([on_exit/2, test/0]).
on_exit(Pid, Fun) ->
spawn(fun() ->
Ref = erlang:monitor(process, Pid),
receive
{'DOWN', Ref, process, Pid, Why} ->
Fun(Why)
end
end).
test() ->
Fun1 = fun() -> receive Msg -> list_to_atom(Msg) end end,
Pid1 = spawn(Fun1),
Fun2 = fun(Why) -> io:format("~w died with error: ~w~n", [Pid1, Why]) end,
_Pid2 = spawn(on_exit, on_exit, [Pid1, Fun2]),
Pid1 ! hello.
在shell中:
1> c(on_exit).
{ok,on_exit}
2> on_exit:test().
<0.39.0> died with error: noproc
hello
3>
=ERROR REPORT==== 9-Apr-2017::05:16:54 ===
Error in process <0.39.0> with exit value: {badarg,[{erlang,list_to_atom,[hello],[]},{on_exit,'-test/0-fun-0-',0,[{file,"on_exit.erl"},{line,14}]}]}
预期产出:
5> Pid1 ! hello.
<0.35.0> died with error: {badarg,[{erlang,list_to_atom,[hello],[]}]}
hello
6>
=ERROR REPORT==== 9-Apr-2017::05:15:47 ===
Error in process <0.35.0> with exit value: {badarg,[{erlang,list_to_atom,[hello],[]}]}
事实上,如果我将test()中的每一行都粘贴到shell中,那么预期的输出就是我所看到的。当我在函数中运行相同的行时,为什么会出现noproc(无进程)错误?
来自docs:
12.8监视器
链接的替代方案是监视器。进程Pid1可以创建一个 通过调用BIF erlang:monitor(进程,Pid2)来监视Pid2。该 函数返回参考Ref。
如果Pid2以退出原因Reason终止,则发送'DOWN'消息 到Pid1:
{'DOWN', Ref, process, Pid2, Reason}
如果Pid2不存在,则立即发送'DOWN'消息 原因设置为noproc 。
答案 0 :(得分:1)
您的代码包含竞争条件 - spawn
是异步的,可能会在生成流程之前返回,并且您可能会在Pid1
开始监控之前发送并崩溃on_exit:on_exit/2
,这导致erlang:monitor/2
调用立即向调用者发送noproc
消息:
1> Pid = spawn(fun() -> ok end).
<0.59.0>
2> erlang:monitor(process, Pid).
#Ref<0.0.1.106>
3> flush().
Shell got {'DOWN',#Ref<0.0.1.106>,process,<0.59.0>,noproc}
ok
代码在shell中运行正常可能是因为Erlang VM在shell中执行的操作比编译代码时慢,但是这种行为无法保证。这是一种典型的竞争条件。
Erlang有一个解决方案:erlang:spawn_monitor/{1,3}
。保证在生成函数后立即连接监视器。您需要重新安排一下代码,而不是使用spawn/3
+ erlang:monitor/1
。