在编程Erlang:并发世界的软件一书的第13章末尾,我们得到了一段代码:
keep_alive(Name, Fun) ->
register(Name, Pid = spawn(Fun)),
on_exit(Pid, fun(_Why) -> keep_alive(Name, Fun) end).
这本书说:
Pid
可能会在执行on_exit
之前死亡。keep_alive
并且Name
的值相同,则代码的竞争条件可能会发生。如果两个程序同时使用相同的keep_alive
来调用Name
,那么register
个调用之一将触发badarg
异常,因为该名称已被取消,调用过程将会死亡。
因此,进程Pid
在on_exit
被调用之前已经死亡,但进程调用on_exit
已经死亡 >
作者的观点是什么,以此为例来描述竞争条件?
答案 0 :(得分:3)
不,其中实际上可能已经死亡。
Pid
引用的过程可能在Fun
的第一行有一个错误,它可能会尝试获取一些外部资源作为其第一个操作,
失败(一个锁定的文件,一个不存在的数据库,无论如何),它可以快速终止非常而不需要循环等。
-module(silly).
-export([do_stuff/0]).
do_stuff() ->
keep_alive(die_bot, fun i_die_fast/0).
i_die_fast() ->
not_ok = io:format("Is this ok?~n"),
receive Anything ->
ok = io:format("Received: ~tp~n", [Anything]),
i_die_fast()
end.
keep_alive(Name, Fun) ->
register(Name, Pid = spawn(Fun)),
on_exit(Pid, fun(_Why) -> keep_alive(Name, Fun) end).
上面的代码如何发挥出来? (io:format/1
始终会返回ok
,因此它与not_ok
不匹配并在第一行崩溃。)i_die_fast/0
似乎永远递归,但它永远不会那么远,可能会在达到on_exit/2
之前死亡。 (但是在on_exit/2
被调用之前,保证不会死!欢迎来到并发。)
重点是你真的不知道。您可以知道的最接近的是使用spawn_link
,或者在较少耦合的情况下使用spawn
,然后使用monitor
或spawn_monitor
。
register
的竞争条件也是如此,可能会使当前正在执行的进程崩溃 - 因此两个竞争条件。
旁注:
这就是为什么我几乎总是拥有衍生的函数寄存器本身,这样如果存在冲突,它就会在孩子的上下文中爆发,而不是调用者(在大多数情况下,你可能有各种各样的原因)想以另一种方式做到这一点):
% Note, we don't need the PID of `some_helper` because it is named.
start() ->
_ = spawn_link(fun() -> some_helper() end),
main_loop().
some_helper() ->
true = register(helper, self()),
helper_loop().