查看源代码 DynamicSupervisor 行为 (Elixir v1.16.2)

一个优化的主管,只在动态情况下启动子进程。

Supervisor 模块旨在处理主要是在主管启动时按指定顺序启动的静态子进程。一个 DynamicSupervisor 启动时没有子进程。相反,子进程是通过 start_child/2 按需启动的,子进程之间没有顺序。这使 DynamicSupervisor 能够通过使用高效的数据结构来容纳数百万个子进程,并能够并发执行某些操作,例如关闭。

示例

动态主管启动时没有子进程,通常有一个名称

children = [
  {DynamicSupervisor, name: MyApp.DynamicSupervisor, strategy: :one_for_one}
]

Supervisor.start_link(children, strategy: :one_for_one)

在子进程规范中给出的选项在 start_link/1 中有说明。

一旦动态主管运行,我们就可以使用它按需启动子进程。给定此示例 GenServer

defmodule Counter do
  use GenServer

  def start_link(initial) do
    GenServer.start_link(__MODULE__, initial)
  end

  def inc(pid) do
    GenServer.call(pid, :inc)
  end

  def init(initial) do
    {:ok, initial}
  end

  def handle_call(:inc, _, count) do
    {:reply, count, count + 1}
  end
end

我们可以使用 start_child/2 和子进程规范来启动一个 Counter 服务器

{:ok, counter1} = DynamicSupervisor.start_child(MyApp.DynamicSupervisor, {Counter, 0})
Counter.inc(counter1)
#=> 0

{:ok, counter2} = DynamicSupervisor.start_child(MyApp.DynamicSupervisor, {Counter, 10})
Counter.inc(counter2)
#=> 10

DynamicSupervisor.count_children(MyApp.DynamicSupervisor)
#=> %{active: 2, specs: 2, supervisors: 0, workers: 2}

可扩展性和分区

DynamicSupervisor 是一个负责启动其他进程的单一进程。在某些应用程序中,DynamicSupervisor 可能会成为瓶颈。为了解决这个问题,您可以启动多个 DynamicSupervisor 实例,然后选择一个“随机”实例来启动子进程。

而不是

children = [
  {DynamicSupervisor, name: MyApp.DynamicSupervisor}
]

DynamicSupervisor.start_child(MyApp.DynamicSupervisor, {Counter, 0})

您可以这样做

children = [
  {PartitionSupervisor,
   child_spec: DynamicSupervisor,
   name: MyApp.DynamicSupervisors}
]

然后

DynamicSupervisor.start_child(
  {:via, PartitionSupervisor, {MyApp.DynamicSupervisors, self()}},
  {Counter, 0}
)

在上面的代码中,我们启动了一个分区主管,它默认会为机器中的每个内核启动一个动态主管。然后,您不是按名称调用 DynamicSupervisor,而是通过分区主管调用它,使用 self() 作为路由键。这意味着每个进程都将分配到一个现有的动态主管。阅读 PartitionSupervisor 文档以了解更多信息。

基于模块的主管

Supervisor 相似,动态主管也支持基于模块的主管。

defmodule MyApp.DynamicSupervisor do
  # Automatically defines child_spec/1
  use DynamicSupervisor

  def start_link(init_arg) do
    DynamicSupervisor.start_link(__MODULE__, init_arg, name: __MODULE__)
  end

  @impl true
  def init(_init_arg) do
    DynamicSupervisor.init(strategy: :one_for_one)
  end
end

请参阅 Supervisor 文档,了解何时可能需要使用基于模块的主管。在 use DynamicSupervisor 之前立即添加的 @doc 注释将附加到生成的 child_spec/1 函数。

use DynamicSupervisor

当您 use DynamicSupervisor 时,DynamicSupervisor 模块将设置 @behaviour DynamicSupervisor 并定义一个 child_spec/1 函数,因此您的模块可以用作监督树中的子进程。

名称注册

主管受限于与 GenServer 相同的名称注册规则。在 GenServer 文档中详细了解这些规则。

从 Supervisor 的 :simple_one_for_one 迁移

如果您之前使用过 Supervisor 模块中已弃用的 :simple_one_for_one 策略,您可以通过几个步骤迁移到 DynamicSupervisor

想象一下给定的“旧”代码

defmodule MySupervisor do
  use Supervisor

  def start_link(init_arg) do
    Supervisor.start_link(__MODULE__, init_arg, name: __MODULE__)
  end

  def start_child(foo, bar, baz) do
    # This will start child by calling MyWorker.start_link(init_arg, foo, bar, baz)
    Supervisor.start_child(__MODULE__, [foo, bar, baz])
  end

  @impl true
  def init(init_arg) do
    children = [
      # Or the deprecated: worker(MyWorker, [init_arg])
      %{id: MyWorker, start: {MyWorker, :start_link, [init_arg]}}
    ]

    Supervisor.init(children, strategy: :simple_one_for_one)
  end
end

它可以像这样升级到 DynamicSupervisor

defmodule MySupervisor do
  use DynamicSupervisor

  def start_link(init_arg) do
    DynamicSupervisor.start_link(__MODULE__, init_arg, name: __MODULE__)
  end

  def start_child(foo, bar, baz) do
    # If MyWorker is not using the new child specs, we need to pass a map:
    # spec = %{id: MyWorker, start: {MyWorker, :start_link, [foo, bar, baz]}}
    spec = {MyWorker, foo: foo, bar: bar, baz: baz}
    DynamicSupervisor.start_child(__MODULE__, spec)
  end

  @impl true
  def init(init_arg) do
    DynamicSupervisor.init(
      strategy: :one_for_one,
      extra_arguments: [init_arg]
    )
  end
end

区别在于 DynamicSupervisor 在调用 start_child/2 时期望子进程规范,而不是在 init 回调中。如果有任何初始化时给出的初始参数,例如 [initial_arg],可以在 DynamicSupervisor.init/1 上的 :extra_arguments 标志中给出。

总结

类型

传递给 start_linkinit/1 函数的选项

start_child 函数的返回值

传递给 start_link 函数的选项

支持的策略

初始化时返回的主管标志

回调

在启动主管和热代码升级期间调用的回调。

函数

返回一个在主管下启动动态主管的规范。

返回包含主管计数值的映射。

接收一组用于初始化动态主管的 options

将子进程规范动态添加到 supervisor 并启动该子进程。

使用给定的选项启动主管。

使用给定的 moduleinit_arg 启动基于模块的主管进程。

使用给定的 reason 同步停止给定的主管。

终止由 pid 标识的给定子进程。

返回包含有关所有子进程的信息的列表。

类型

@type init_option() ::
  {:strategy, strategy()}
  | {:max_restarts, non_neg_integer()}
  | {:max_seconds, pos_integer()}
  | {:max_children, non_neg_integer() | :infinity}
  | {:extra_arguments, [term()]}

传递给 start_linkinit/1 函数的选项

@type on_start_child() ::
  {:ok, pid()}
  | {:ok, pid(), info :: term()}
  | :ignore
  | {:error, {:already_started, pid()} | :max_children | term()}

start_child 函数的返回值

@type option() :: GenServer.option()

传递给 start_link 函数的选项

@type strategy() :: :one_for_one

支持的策略

@type sup_flags() :: %{
  strategy: strategy(),
  intensity: non_neg_integer(),
  period: pos_integer(),
  max_children: non_neg_integer() | :infinity,
  extra_arguments: [term()]
}

初始化时返回的主管标志

回调

@callback init(init_arg :: term()) :: {:ok, sup_flags()} | :ignore

在启动主管和热代码升级期间调用的回调。

开发人员通常在 init 回调的末尾调用 DynamicSupervisor.init/1 以返回正确的主管标志。

函数

链接到此函数

child_spec(options)

查看源代码 (自 1.6.1 起)

返回一个在主管下启动动态主管的规范。

它接受与 start_link/1 相同的选项。

请参阅 Supervisor,了解有关子进程规范的更多信息。

链接到此函数

count_children(supervisor)

查看源代码 (自 1.6.0 起)
@spec count_children(Supervisor.supervisor()) :: %{
  specs: non_neg_integer(),
  active: non_neg_integer(),
  supervisors: non_neg_integer(),
  workers: non_neg_integer()
}

返回包含主管计数值的映射。

该映射包含以下键

  • :specs - 子进程的数量

  • :active - 此主管管理的所有活动运行的子进程的计数

  • :supervisors - 所有主管的计数,无论子进程是否还活着

  • :workers - 所有工作进程的计数,无论子进程是否还活着

链接到此函数

init(options)

查看源代码 (自 1.6.0 起)
@spec init([init_option()]) :: {:ok, sup_flags()}

接收一组用于初始化动态主管的 options

这通常在基于模块的主管的 init/1 回调的末尾调用。请参阅模块文档中的“基于模块的主管”部分以了解更多信息。

它接受与 start_link/1 相同的 options:name 除外),并返回包含主管选项的元组。

示例

def init(_arg) do
  DynamicSupervisor.init(max_children: 1000)
end
链接到此函数

start_child(supervisor, child_spec)

查看源代码 (自 1.6.0 起)
@spec start_child(
  Supervisor.supervisor(),
  Supervisor.child_spec()
  | {module(), term()}
  | module()
  | (old_erlang_child_spec :: :supervisor.child_spec())
) :: on_start_child()

将子进程规范动态添加到 supervisor 并启动该子进程。

child_spec 应该是一个有效的子进程规范,如 Supervisor 文档的“子进程规范”部分所述。子进程将按子进程规范中定义的启动。请注意,虽然 :id 字段在规范中仍然是必需的,但该值将被忽略,因此不需要是唯一的。

如果子进程启动函数返回 {:ok, child}{:ok, child, info},则子进程规范和 PID 将添加到主管,此函数返回相同的值。

如果子进程启动函数返回 :ignore,则不会将任何子进程添加到监督树中,此函数也将返回 :ignore

如果子进程启动函数返回错误元组或错误值,或者它失败,则子进程规范将被丢弃,此函数返回 {:error, error},其中 error 是子进程启动函数返回的错误或错误值,或者如果它失败则返回失败原因。

如果主管已经以 N 超过在主管初始化时设置的 :max_children 数量的方式拥有 N 个子进程(请参阅 init/1),则此函数返回 {:error, :max_children}

链接到此函数

start_link(options)

查看源代码 (自 1.6.0 起)
@spec start_link([option() | init_option()]) :: Supervisor.on_start()

使用给定的选项启动主管。

此函数通常不会直接调用,而是使用 DynamicSupervisor 作为另一个主管的子进程时调用。

children = [
  {DynamicSupervisor, name: MySupervisor}
]

如果主管成功生成,此函数将返回 {:ok, pid},其中 pid 是主管的 PID。如果主管被赋予一个名称,并且已经存在具有指定名称的进程,该函数将返回 {:error, {:already_started, pid}},其中 pid 是该进程的 PID。

请注意,使用此函数启动的监督进程与父进程关联,不仅会在发生崩溃时退出,还会在父进程以 :normal 原因退出时退出。

选项

  • :name - 以给定名称注册监督进程。支持的值在 GenServer 模块文档的“名称注册”部分中描述。

  • :strategy - 重启策略选项。唯一支持的值是 :one_for_one,这意味着如果子进程终止,则不会终止其他子进程。您可以在 Supervisor 模块文档中详细了解策略。

  • :max_restarts - 在一段时间内允许的最大重启次数。默认为 3

  • :max_seconds - :max_restarts 适用的时间段。默认为 5

  • :max_children - 此监督进程下同时运行的子进程的最大数量。当超过 :max_children 时,start_child/2 返回 {:error, :max_children}。默认为 :infinity

  • :extra_arguments - 在传递给 start_child/2 的子进程规范中指定的参数之前预先添加的参数。默认为空列表。

链接到此函数

start_link(module, init_arg, opts \\ [])

查看源代码 (自 1.6.0 版本起)
@spec start_link(module(), term(), [option()]) :: Supervisor.on_start()

使用给定的 moduleinit_arg 启动基于模块的主管进程。

要启动监督进程,将在给定的 module 中调用 init/1 回调函数,并以 init_arg 作为其参数。 init/1 回调函数必须返回一个监督进程规范,可以使用 init/1 函数创建。

如果 init/1 回调函数返回 :ignore,此函数也将返回 :ignore,并且监督进程以 :normal 原因终止。如果它失败或返回不正确的值,此函数将返回 {:error, term},其中 term 是包含错误信息的项,并且监督进程以 term 原因终止。

还可以使用 :name 选项来注册监督进程名称,支持的值在 GenServer 模块文档的“名称注册”部分中描述。

如果主管成功生成,此函数将返回 {:ok, pid},其中 pid 是主管的 PID。如果主管被赋予一个名称,并且已经存在具有指定名称的进程,该函数将返回 {:error, {:already_started, pid}},其中 pid 是该进程的 PID。

请注意,使用此函数启动的监督进程与父进程关联,不仅会在发生崩溃时退出,还会在父进程以 :normal 原因退出时退出。

链接到此函数

stop(supervisor, reason \\ :normal, timeout \\ :infinity)

查看源代码 (自 1.7.0 版本起)
@spec stop(Supervisor.supervisor(), reason :: term(), timeout()) :: :ok

使用给定的 reason 同步停止给定的主管。

如果监督进程以给定原因终止,则返回 :ok。如果它以其他原因终止,则调用将退出。

此函数保持 OTP 语义,有关错误报告。如果原因不是 :normal:shutdown{:shutdown, _},则会记录错误报告。

链接到此函数

terminate_child(supervisor, pid)

查看源代码 (自 1.6.0 版本起)
@spec terminate_child(Supervisor.supervisor(), pid()) :: :ok | {:error, :not_found}

终止由 pid 标识的给定子进程。

如果成功,此函数返回 :ok。如果没有具有给定 PID 的进程,此函数将返回 {:error, :not_found}

链接到此函数

which_children(supervisor)

查看源代码 (自 1.6.0 版本起)
@spec which_children(Supervisor.supervisor()) :: [
  {:undefined, pid() | :restarting, :worker | :supervisor,
   [module()] | :dynamic}
]

返回包含有关所有子进程的信息的列表。

请注意,在低内存条件下监督大量子进程时调用此函数可能会导致内存不足异常。

此函数返回一个包含以下内容的元组列表

  • id - 对于动态监督进程,它始终为 :undefined

  • child - 对应子进程的 PID,如果进程即将重启,则为原子 :restarting

  • type - 子进程规范中定义的 :worker:supervisor

  • modules - 子进程规范中定义的