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行為的形式,為這類功能提供了抽象化。
其他圍繞行程的抽象化,例如Agent、GenServer或Task,都提供兩種啟動方式:start或start_link。