繁体   English   中英

在 GenServer.start_link/3 中使用 {:via, module, term} 注册名称有什么好处?

[英]What's the benefit of registering name using {:via, module, term} in GenServer.start_link/3?

GenServer.start_link/3我可以使用原子在本地注册一个名称,用于这样的过程:

defmodule Worker do
  use GenServer

  def start_link do
    GenServer.start_link(__MODULE__, nil, name: :worker)
  end
end

然后我可以启动一个主管来监督这个过程:

defmodule Boss do
  use Supervisor

  def init(_) do
    children = [worker(Worker, [])]
    supervise(children, strategy: :one_for_one)
  end
end

现在我想让监督者监督 3 个Worker进程,所以我需要给这 3 个进程赋予唯一的名称,以便监督者重新启动进程时,它将始终使用相同的符号名称。

我可以简单地对唯一的Worker进程名称使用字符串插值,如下所示:

defmodule Worker do
  use GenServer

  def start_link(id) do
    GenServer.start_link(__MODULE__, nil, name: :"worker_#{id}")
  end
end

然后像这样监督3个进程:

defmodule Boss do
  use Supervisor

  def init(_) do
    children = for id <- 1..3 do
      worker(Worker, [id], id: id)
    end
    supervise(children, strategy: :one_for_one)
  end
end

它像预期的那样工作。

名称注册”部分下的 GenServer 文档中,它说您也可以使用{:via, module, term}来注册名称。

{:via, module, term} - GenServer 使用给定的机制和名称注册。 :via选项需要一个导出register_name/2unregister_name/1whereis_name/1send/2 一个这样的例子是:global模块,它使用这些函数来保存进程的名称列表及其相关联的 PID,这些列表在 Elixir 节点网络全局可用。 Elixir 还附带一个名为 Registry 的本地、分散且可扩展的注册表,用于在本地存储动态生成的名称。

但是,为了使用:via选项,您必须实现一个导出register_name/2unregister_name/1whereis_name/1send/2 ,与简单地使用如上所示的字符串插值技术相比,这似乎非常麻烦。

所以我的问题是:

  1. 使用 {:via, module, term} 注册名称比简单地使用字符串插值有什么好处?
  2. 是否有使用:via选项注册名称的实用示例?

tl;dr - :via是否允许您使用非标准进程注册库。 它们必须符合接口(很像在 Java 中实现接口),并且可以提供额外的功能。

主要示例是当您要使用非标准名称注册库时。 gproc 库为例。 它遵循使用:via的接口要求,因此对您的应用程序代码的侵入最少。 此外,与标准名称注册系统相比,它具有以下几个优点:

  1. 使用任何术语作为进程别名
  2. 在多个别名下注册一个进程
  3. 多个进程可以同时注册非唯一属性; 查询级别理解 (QLC)和匹配规范接口,用于对字典进行高效查询
  4. 等待注册,让你等一个进程注册自己
  5. 原子地将注册的名称和属性赠送给另一个进程
  6. 计数器和聚合计数器,它们自动维护具有给定名称的所有计数器的总数
  7. 全局注册,将上述所有功能应用于节点网络

Elixir 的Registry模块是另一个需要 via 元组的示例。

一种情况是,当您想动态地为您的工作人员分配名称时(也许他们由DynamicSupervisorsimple_one_for_one主管监督,并且随着时间的推移动态产生)。

因为原子永远不会被垃圾收集,所以你不能将它们用作那些工人的名字,否则你将不得不处理内存泄漏。

出于某种原因,在:global模块上注册名称让我感到不舒服,因为全局状态通常被认为是邪恶的,尤其是在高度并发的环境中(这就是您选择 Erlang/Elixir 的原因)。

所以在这种情况下,最好在“命名空间”中注册名称。 {:via, module, term}变体在这种情况下大放异彩。 module用作命名空间, term可以是任何东西(通常是字符串,因为它易于理解,并且会被垃圾收集)。

顺便说一句,如果您不想自己实现注册表,那么已经有一个模块Registry专门用于此目的。 您只需要为 Registry 进程命名并对其进行监督。

使用:via元组可以很好地封装别名处理,并为您提供一个可以发现进程的固定点。 此外, :via元组可以是任意复杂的,例如像{:my_worker, 1}这样的元组通常比处理字符串操作更好用。

(请注意,我正在学习 Elixir,所以不要相信我的话。此外, :via tuples 可能有更强/更好的论据。)

我有同样的问题。 我可以想到两个原因,为什么您要使用Registry模块而不是生成动态名称,例如:"worker:#{id}"

  • 原子不是垃圾收集
  • 根据注册表文档:“注册表中的每个条目都与注册密钥的进程相关联。如果进程崩溃,与该进程关联的密钥将自动删除。”

因此,注册表似乎链接到其中的进程,如果这些进程失败,它将删除条目:

iex(6)> {:ok, _} = Registry.start_link(keys: :unique, name: Registry.ViaTest2)
{:ok, #PID<0.445.0>}
iex(7)> name = {:via, Registry, {Registry.ViaTest2, "agent"}}
{:via, Registry, {Registry.ViaTest2, "agent"}}
iex(8)> {:ok, agent} = Agent.start(fn -> 0 end, name: name)
{:ok, #PID<0.449.0>}
iex(9)> Registry.lookup(Registry.ViaTest2, "agent")
[{#PID<0.449.0>, nil}]
iex(10)> Process.alive?(agent)
true
iex(11)> Process.exit(agent, :kill)
true
iex(12)> Process.alive?(agent)
false
iex(13)> Registry.lookup(Registry.ViaTest2, "agent")
[]

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM