連結

連結 在 Elixir

1 個練習

關於 連結

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。

透過 GitHub 編輯 連結會在新視窗或分頁中開啟

學習 連結