在 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/2
、unregister_name/1
、whereis_name/1
和send/2
。一个这样的例子是:global
模块,它使用这些 用于保存进程名称及其列表的函数 Elixir 网络全球可用的相关 PID 节点。 Elixir 还附带了一个本地的、去中心化的和可扩展的 名为 Registry 的注册表,用于在本地存储生成的名称 动态的。
但是,为了使用 :via
选项,您必须实现一个导出 register_name/2
、unregister_name/1
、whereis_name/1
和 send/2
的模块,与简单地使用字符串插值技术相比,这似乎相当麻烦,如图所示以上。
所以我的问题是:
-
与简单地使用字符串插值相比,使用 :via, module, term 注册名称有什么好处?
有没有使用
:via
选项注册名字的实用例子?
【问题讨论】:
【参考方案1】:tl;dr - :via
允许您使用非标准流程注册库。它们必须符合接口(很像在 Java 中实现接口),并且可以提供额外的功能。
主要示例是当您要使用非标准名称注册库时。以gproc library 为例。它遵循使用:via
的接口要求,因此需要对您的应用程序代码进行最少的入侵。此外,与标准名称注册系统相比,它还具有以下几个优点:
-
使用任何术语作为进程别名
在多个别名下注册一个进程
多个进程可以同时注册非唯一属性; query level comprehension (QLC) 和匹配规范接口,实现对字典的高效查询
等待注册,让我们等到一个进程自己注册
以原子方式将注册的名称和属性提供给另一个进程
计数器和聚合计数器,自动维护具有给定名称的所有计数器的总数
全局注册表,将上述所有功能应用于节点网络
Elixir 的 Registry
模块是另一个需要 via 元组的示例。
【讨论】:
【参考方案2】:一种情况是,当您想要动态地为您的工作人员分配名称时(可能他们由 DynamicSupervisor
或 simple_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"
)的两个原因
因此,注册表似乎链接到其中的进程,如果这些进程失败,它将删除条目:
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游记