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

Posted

技术标签:

【中文标题】在 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 中使用 :via, module, term 注册名称有什么好处? 【发布时间】:2016-12-25 01:11:15 【问题描述】:

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

defmodule Worker do
  use GenServer

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

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

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

它按预期工作。

在"Name registration" section 下的GenServer 文档中,它说您也可以使用:via, module, term 来注册名称。

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

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

所以我的问题是:

    与简单地使用字符串插值相比,使用 :via, module, term 注册名称有什么好处? 有没有使用:via选项注册名字的实用例子?

【问题讨论】:

【参考方案1】:

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

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

    使用任何术语作为进程别名 在多个别名下注册一个进程 多个进程可以同时注册非唯一属性; query level comprehension (QLC) 和匹配规范接口,实现对字典的高效查询 等待注册,让我们等到一个进程自己注册 以原子方式将注册的名称和属性提供给另一个进程 计数器和聚合计数器,自动维护具有给定名称的所有计数器的总数 全局注册表,将上述所有功能应用于节点网络

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

【讨论】:

【参考方案2】:

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

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

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

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

顺便说一句,如果您不想自己实现注册表,那么已经有一个模块Registry 就是为了这个目的。您只需要给 Registry 进程起一个名称并对其进行监督。

【讨论】:

【参考方案3】:

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

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

【讨论】:

【参考方案4】:

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

原子不会被垃圾回收 根据Registry docs:“注册表中的每个条目都与已注册密钥的进程相关联。如果进程崩溃,与该进程关联的密钥将自动删除。”

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

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")
[]

【讨论】:

以上是关于在 GenServer.start_link/3 中使用 :via, module, term 注册名称有啥好处?的主要内容,如果未能解决你的问题,请参考以下文章

NOIP 2015 & SDOI 2016 Round1 & CTSC 2016 & SDOI2016 Round2游记

秋的潇洒在啥?在啥在啥?

上传的数据在云端的怎么查看,保存在啥位置?

在 React 应用程序中在哪里转换数据 - 在 Express 中还是在前端使用 React?

存储在 plist 中的数据在模拟器中有效,但在设备中无效

如何在保存在 Mongoose (ExpressJS) 之前在模型中格式化数据