リン

リンク の 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

この動作は、プロセス同士を_リンク_することで変えられます。2つのプロセスがリンクされていると、片方で起きた障害がもう片方にも伝播します。リンクは双方向です。

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>]

プロセスをリンクすると、並列化した処理を行うときに役立ちます。あるまとまりの処理が完了しなかったときに、ほかのまとまりの処理を続けるべきではない、という場面です。

exitのトラップ

リンクは、プロセスを_監視_するためにも使えます。プロセスが_exitをトラップ_していると、リンクされているプロセスがクラッシュしても、自分はクラッシュしません。代わりに、クラッシュに関するメッセージを受け取ります。これにより、たとえばクラッシュしたプロセスを再起動するなど、クラッシュに穏当に対処できます。

プロセスがexitをトラップするように設定するには、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

exitをトラップすると、そのプロセスは、リンクされている_すべての_プロセスで起きたクラッシュの影響を受けなくなります。また、メールボックスにあるexitメッセージを読まないままでいると、メッセージがたまっていく可能性があります。そのため、exitをトラップするプロセスにリンクされているプロセスはすべて把握しておく必要があります。ふつう、exitをトラップするプロセスはほかのプロセスの監視だけを担当し、それ以外のことはしません。

実際には、監視用のプロセスを自分で一から実装することはないでしょう。Elixirには、こうした機能のための抽象化としてSupervisorビヘイビアが用意されています。

プロセスまわりのほかの抽象化、たとえばAgent、GenServer、Taskなどでは、起動方法が2つ用意されています。startかstart_linkです。

GitHubで編集 リンクは新しいウィンドウまたはタブで開きます

リンクを学習する