Elixir 进程彼此隔离,默认情况下不共享任何东西。当未链接的子进程崩溃时,它的父进程不会受到影响。
parent_pid =
spawn(fn ->
spawn(fn -> raise "oops" end)
receive do
message -> IO.inspect(message, label: "received message")
end
end)
# => 20:03:08.405 [error] Process #PID<0.153.0> raised an exception
# ** (RuntimeError) oops
# (stdlib 3.13.2) erl_eval.erl:678: :erl_eval.do_apply/6
Process.alive?(parent_pid)
# => true
这种行为可以通过把进程相互_链接_来改变。如果两个进程被链接在一起,其中一个进程出错就会传播到另一个进程。链接是双向的。
parent_pid =
spawn(fn ->
spawn_link(fn -> raise "oops" end)
receive do
message ->
IO.inspect(message, label: "received message")
end
end)
# => 20:05:34.125 [error] Process #PID<0.171.0> raised an exception
# ** (RuntimeError) oops
# (stdlib 3.13.2) erl_eval.erl:678: :erl_eval.do_apply/6
Process.alive?(parent_pid)
# => false
可以使用spawn_link/1在创建进程的同时就把它链接到调用进程,这是一个原子操作;也可以之后再用Process.link/1把它们链接起来。
可以用Process.info/1查看当前有哪些进程链接到某个进程(并且仍在运行)。
pid = spawn_link(fn -> :timer.sleep(50_000) end)
# => #PID<0.126.0>
self()
|> Process.info()
|> Keyword.get(:links)
# => [#PID<0.126.0>]
当你的工作是并行化的时候,如果某个任务块在另一块没能完成时不应该继续执行下去,那么把进程链接起来就会很有用。
链接也可以用来_监督_进程。如果一个进程_捕获退出_,那么当与它链接的某个进程崩溃时,它自己不会崩溃,而是会收到一条关于这次崩溃的消息。这使它能够从容地处理崩溃,例如重启那个崩溃的进程。
可以通过调用Process.flag(:trap_exit, true)把进程配置为捕获退出。注意,Process.flag/2返回的是标志的_旧_值,而不是新值。
可以用Process.info/1查看该标志的当前值。
pid
|> Process.info()
|> Keyword.get(:trap_exit)
# => false
当被链接的进程崩溃时,发送给该进程的消息会匹配模式{:EXIT, from, reason},其中from是一个 PID。如果reason不是原子:normal,那就说明该进程崩溃了,或者被强制杀死了。
parent_pid =
spawn(fn ->
Process.flag(:trap_exit, true)
spawn_link(fn -> raise "oops" end)
receive do
message ->
IO.inspect(message, label: "received message")
# do something else,
# to demonstrate that the process stays alive
:timer.sleep(50_000)
end
end)
# => 20:05:34.125 [error] Process #PID<0.295.0> raised an exception
# ** (RuntimeError) oops
# (stdlib 3.13.2) erl_eval.erl:678: :erl_eval.do_apply/6
# received message: {:EXIT, #PID<0.295.0>,
# {%RuntimeError{message: "oops"},
# [{:erl_eval, :do_apply, 6, [file: 'erl_eval.erl', line: 678]}]}}
Process.alive?(parent_pid)
# => true
注意,捕获退出会让这个进程对_所有_已链接进程的崩溃都免疫,而且退出消息如果一直不读取,可能会在进程的邮箱里越积越多。因此,如果一个进程捕获了退出,你就应该跟踪所有链接到它的进程。通常,捕获退出的进程只负责监督其他进程,不做别的事情。
实际上,你并不会自己从零开始实现一个监督进程。Elixir 以 Supervisor behaviour 的形式为这类功能提供了一种抽象。
围绕进程的其他抽象,比如Agent、GenServer或Task,提供了两种启动它们的方式:start或start_link。