簡體   English   中英

在 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我可以使用原子在本地注冊一個名稱,用於這樣的過程:

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/2unregister_name/1whereis_name/1send/2 一個這樣的例子是:global模塊,它使用這些函數來保存進程的名稱列表及其相關聯的 PID,這些列表在 Elixir 節點網絡全局可用。 Elixir 還附帶一個名為 Registry 的本地、分散且可擴展的注冊表,用於在本地存儲動態生成的名稱。

但是,為了使用:via選項,您必須實現一個導出register_name/2unregister_name/1whereis_name/1send/2 ,與簡單地使用如上所示的字符串插值技術相比,這似乎非常麻煩。

所以我的問題是:

  1. 使用 {:via, module, term} 注冊名稱比簡單地使用字符串插值有什么好處?
  2. 是否有使用:via選項注冊名稱的實用示例?

tl;dr - :via是否允許您使用非標准進程注冊庫。 它們必須符合接口(很像在 Java 中實現接口),並且可以提供額外的功能。

主要示例是當您要使用非標准名稱注冊庫時。 gproc 庫為例。 它遵循使用:via的接口要求,因此對您的應用程序代碼的侵入最少。 此外,與標准名稱注冊系統相比,它具有以下幾個優點:

  1. 使用任何術語作為進程別名
  2. 在多個別名下注冊一個進程
  3. 多個進程可以同時注冊非唯一屬性; 查詢級別理解 (QLC)和匹配規范接口,用於對字典進行高效查詢
  4. 等待注冊,讓你等一個進程注冊自己
  5. 原子地將注冊的名稱和屬性贈送給另一個進程
  6. 計數器和聚合計數器,它們自動維護具有給定名稱的所有計數器的總數
  7. 全局注冊,將上述所有功能應用於節點網絡

Elixir 的Registry模塊是另一個需要 via 元組的示例。

一種情況是,當您想動態地為您的工作人員分配名稱時(也許他們由DynamicSupervisorsimple_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.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM