简体   繁体   中英

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:

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.

I can simply use string interpolation for the unique Worker process name like this:

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:

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.

{:via, module, term} - the GenServer is registered with the given mechanism and name. The :via option expects a module that exports register_name/2 , unregister_name/1 , whereis_name/1 , and send/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. Elixir also ships with a local, decentralized and scalable registry called Registry for locally storing names that are generated dynamically.

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.

So my question is:

  1. What's the benefit of registering name using {:via, module, term} over simply using string interpolation?
  2. Is there a pragmatic example of using :via option to register name?

tl;dr - :via is there to allow you to use non-standard process registration libraries. They must conform to an interface (much like implementing an interface in Java), and may provide extra functionality.

The main example is when you want to use a non-standard name registration library. Take for example the gproc library . It follows the interface requirements to use :via , so minimal intrusion is required into your application code. 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
  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.

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).

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).

So in this case, it's better to register the names in a "namespace". The {:via, module, term} variation shines in such cases. The module serves as a namespace, and term can be anything (usually a string because it's easy to understand, and is garbage collected).

BTW, if you don't want to implement the registry yourself, there is already a module Registry just for this purpose. You just need to give the Registry process a name and supervise it.

Using :via tuples allows you to nicely encapsulate the the alias handling, and gives you a fixed point where you can discover processes. 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.

(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.)

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}"

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

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

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