查看源代码 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
标志中给出。
总结
回调
在启动主管和热代码升级期间调用的回调。
函数
返回一个在主管下启动动态主管的规范。
返回包含主管计数值的映射。
接收一组用于初始化动态主管的 options
。
将子进程规范动态添加到 supervisor
并启动该子进程。
使用给定的选项启动主管。
使用给定的 module
和 init_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_link
和 init/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()] }
初始化时返回的主管标志
回调
在启动主管和热代码升级期间调用的回调。
开发人员通常在 init 回调的末尾调用 DynamicSupervisor.init/1
以返回正确的主管标志。
函数
返回一个在主管下启动动态主管的规范。
它接受与 start_link/1
相同的选项。
请参阅 Supervisor
,了解有关子进程规范的更多信息。
@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
- 所有工作进程的计数,无论子进程是否还活着
@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
@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}
。
@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
的子进程规范中指定的参数之前预先添加的参数。默认为空列表。
@spec start_link(module(), term(), [option()]) :: Supervisor.on_start()
使用给定的 module
和 init_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
原因退出时退出。
@spec stop(Supervisor.supervisor(), reason :: term(), timeout()) :: :ok
使用给定的 reason
同步停止给定的主管。
如果监督进程以给定原因终止,则返回 :ok
。如果它以其他原因终止,则调用将退出。
此函数保持 OTP 语义,有关错误报告。如果原因不是 :normal
、:shutdown
或 {:shutdown, _}
,则会记录错误报告。
@spec terminate_child(Supervisor.supervisor(), pid()) :: :ok | {:error, :not_found}
终止由 pid
标识的给定子进程。
如果成功,此函数返回 :ok
。如果没有具有给定 PID 的进程,此函数将返回 {:error, :not_found}
。
@spec which_children(Supervisor.supervisor()) :: [ {:undefined, pid() | :restarting, :worker | :supervisor, [module()] | :dynamic} ]
返回包含有关所有子进程的信息的列表。
请注意,在低内存条件下监督大量子进程时调用此函数可能会导致内存不足异常。
此函数返回一个包含以下内容的元组列表
id
- 对于动态监督进程,它始终为:undefined
child
- 对应子进程的 PID,如果进程即将重启,则为原子:restarting
type
- 子进程规范中定义的:worker
或:supervisor
modules
- 子进程规范中定义的