链接

链接 属于 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 behaviour 的形式为这类功能提供了一种抽象。

围绕进程的其他抽象,比如Agent、GenServer或Task,提供了两种启动它们的方式:start或start_link。

通过 GitHub 编辑 该链接会在新窗口或标签页中打开

学习 链接