[英]Trouble registering a GenServer with a via tuple. ** (UndefinedFunctionError) function PokerServer.whereis_name/1 is undefined or private)
[英]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/2
、unregister_name/1
、whereis_name/1
和send/2
。 一个这样的例子是:global
模块,它使用这些函数来保存进程的名称列表及其相关联的 PID,这些列表在 Elixir 节点网络全局可用。 Elixir 还附带一个名为 Registry 的本地、分散且可扩展的注册表,用于在本地存储动态生成的名称。
但是,为了使用:via
选项,您必须实现一个导出register_name/2
、 unregister_name/1
、 whereis_name/1
和send/2
,与简单地使用如上所示的字符串插值技术相比,这似乎非常麻烦。
所以我的问题是:
:via
选项注册名称的实用示例? tl;dr - :via
是否允许您使用非标准进程注册库。 它们必须符合接口(很像在 Java 中实现接口),并且可以提供额外的功能。
主要示例是当您要使用非标准名称注册库时。 以gproc 库为例。 它遵循使用:via
的接口要求,因此对您的应用程序代码的侵入最少。 此外,与标准名称注册系统相比,它具有以下几个优点:
Elixir 的Registry
模块是另一个需要 via 元组的示例。
一种情况是,当您想动态地为您的工作人员分配名称时(也许他们由DynamicSupervisor
或simple_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.