简体   繁体   English

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

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

In GenServer.start_link/3 I can register a name locally using an atom for a process like this:GenServer.start_link/3我可以使用原子在本地注册一个名称,用于这样的过程:

defmodule Worker do
  use GenServer

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

Then I can start a supervisor to supervise this process:然后我可以启动一个主管来监督这个过程:

defmodule Boss do
  use Supervisor

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

Now I want to make the supervisor to supervise 3 Worker processes, so I need to give those 3 processes unique names, so that when supervisor restarts the process it will always use the same symbolic name.现在我想让监督者监督 3 个Worker进程,所以我需要给这 3 个进程赋予唯一的名称,以便监督者重新启动进程时,它将始终使用相同的符号名称。

I can simply use string interpolation for the unique Worker process name like this:我可以简单地对唯一的Worker进程名称使用字符串插值,如下所示:

defmodule Worker do
  use GenServer

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

Then supervise 3 processes like this:然后像这样监督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

It works like expected.它像预期的那样工作。

In the doc for GenServer under " Name registration " section , it says you can use {:via, module, term} to register a name as well.名称注册”部分下的 GenServer 文档中,它说您也可以使用{:via, module, term}来注册名称。

{:via, module, term} - the GenServer is registered with the given mechanism and name. {:via, module, term} - GenServer 使用给定的机制和名称注册。 The :via option expects a module that exports register_name/2 , unregister_name/1 , whereis_name/1 , and send/2 . :via选项需要一个导出register_name/2unregister_name/1whereis_name/1send/2 One such example is the :global module which uses these functions for keeping the list of names of processes and their associated PIDs that are available globally for a network of Elixir nodes.一个这样的例子是:global模块,它使用这些函数来保存进程的名称列表及其相关联的 PID,这些列表在 Elixir 节点网络全局可用。 Elixir also ships with a local, decentralized and scalable registry called Registry for locally storing names that are generated dynamically. Elixir 还附带一个名为 Registry 的本地、分散且可扩展的注册表,用于在本地存储动态生成的名称。

However, in order to use :via option you have to implement a module that exports register_name/2 , unregister_name/1 , whereis_name/1 and send/2 , which seems pretty cumbersome comparing to simply use string interpolation technique as shown above.但是,为了使用:via选项,您必须实现一个导出register_name/2unregister_name/1whereis_name/1send/2 ,与简单地使用如上所示的字符串插值技术相比,这似乎非常麻烦。

So my question is:所以我的问题是:

  1. What's the benefit of registering name using {:via, module, term} over simply using string interpolation?使用 {:via, module, term} 注册名称比简单地使用字符串插值有什么好处?
  2. Is there a pragmatic example of using :via option to register name?是否有使用:via选项注册名称的实用示例?

tl;dr - :via is there to allow you to use non-standard process registration libraries. tl;dr - :via是否允许您使用非标准进程注册库。 They must conform to an interface (much like implementing an interface in Java), and may provide extra functionality.它们必须符合接口(很像在 Java 中实现接口),并且可以提供额外的功能。

The main example is when you want to use a non-standard name registration library.主要示例是当您要使用非标准名称注册库时。 Take for example the gproc library .gproc 库为例。 It follows the interface requirements to use :via , so minimal intrusion is required into your application code.它遵循使用:via的接口要求,因此对您的应用程序代码的侵入最少。 In addition, it provides several advantages over the standard name registration system:此外,与标准名称注册系统相比,它具有以下几个优点:

  1. Use any term as a process alias使用任何术语作为进程别名
  2. Register a process under several aliases在多个别名下注册一个进程
  3. Non-unique properties can be registered simultaneously by many processes;多个进程可以同时注册非唯一属性; query level comprehension (QLC) and match specification interface for efficient queries on the dictionary查询级别理解 (QLC)和匹配规范接口,用于对字典进行高效查询
  4. Await registration, let's you wait until a process registers itself等待注册,让你等一个进程注册自己
  5. Atomically give away registered names and properties to another process原子地将注册的名称和属性赠送给另一个进程
  6. Counters, and aggregated counters, which automatically maintain the total of all counters with a given name计数器和聚合计数器,它们自动维护具有给定名称的所有计数器的总数
  7. Global registry, with all the above functions applied to a network of nodes全局注册,将上述所有功能应用于节点网络

Elixir's Registry module is another example of one which requires a via tuple. Elixir 的Registry模块是另一个需要 via 元组的示例。

One scenario is that when you want to assign names to your workers dynamically (maybe they are supervised by a DynamicSupervisor or a simple_one_for_one supervisor and are dynamically spawned over time).一种情况是,当您想动态地为您的工作人员分配名称时(也许他们由DynamicSupervisorsimple_one_for_one主管监督,并且随着时间的推移动态产生)。

Because atoms are never garbage collected , you can't use them as the names of those workers, otherwise you'll have to deal with memory leak.因为原子永远不会被垃圾收集,所以你不能将它们用作那些工人的名字,否则你将不得不处理内存泄漏。

For some reason, registering names on :global module makes me feel uncomfortable because global states are often considered evil, especially in a highly concurrent environment (which is why you choose Erlang/Elixir).出于某种原因,在:global模块上注册名称让我感到不舒服,因为全局状态通常被认为是邪恶的,尤其是在高度并发的环境中(这就是您选择 Erlang/Elixir 的原因)。

So in this case, it's better to register the names in a "namespace".所以在这种情况下,最好在“命名空间”中注册名称。 The {:via, module, term} variation shines in such cases. {:via, module, term}变体在这种情况下大放异彩。 The module serves as a namespace, and term can be anything (usually a string because it's easy to understand, and is garbage collected). module用作命名空间, term可以是任何东西(通常是字符串,因为它易于理解,并且会被垃圾收集)。

BTW, if you don't want to implement the registry yourself, there is already a module Registry just for this purpose.顺便说一句,如果您不想自己实现注册表,那么已经有一个模块Registry专门用于此目的。 You just need to give the Registry process a name and supervise it.您只需要为 Registry 进程命名并对其进行监督。

Using :via tuples allows you to nicely encapsulate the the alias handling, and gives you a fixed point where you can discover processes.使用:via元组可以很好地封装别名处理,并为您提供一个可以发现进程的固定点。 In addition, the :via tuples can be arbitrarily complex, eg a tuple such as {:my_worker, 1} which will usually be nicer to work with than messing around with string manipulation.此外, :via元组可以是任意复杂的,例如像{:my_worker, 1}这样的元组通常比处理字符串操作更好用。

(Note that I'm learning Elixir, so don't take my word for it. In addition, there could be stronger/better arguments for :via tuples.) (请注意,我正在学习 Elixir,所以不要相信我的话。此外, :via tuples 可能有更强/更好的论据。)

I had this same question.我有同样的问题。 I can think of 2 reasons why you'd want to use the Registry module instead of generating a dynamic name such as :"worker:#{id}"我可以想到两个原因,为什么您要使用Registry模块而不是生成动态名称,例如:"worker:#{id}"

  • atoms are not garbage collected原子不是垃圾收集
  • according to the Registry docs : "Each entry in the registry is associated to the process that has registered the key. If the process crashes, the keys associated to that process are automatically removed."根据注册表文档:“注册表中的每个条目都与注册密钥的进程相关联。如果进程崩溃,与该进程关联的密钥将自动删除。”

So it seems that the registry links to the processes that are in it, and will remove the entries if those processes fail:因此,注册表似乎链接到其中的进程,如果这些进程失败,它将删除条目:

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