查看源代码 Registry (Elixir v1.16.2)

本地、分散式且可扩展的键值进程存储。

它允许开发者通过给定键查找一个或多个进程。如果注册表具有 :unique 键,则一个键指向 0 或 1 个进程。如果注册表允许 :duplicate 键,则单个键可能指向任意数量的进程。在这两种情况下,不同的键都可能标识相同的进程。

注册表中的每个条目都与注册了该键的进程相关联。如果该进程崩溃,与该进程关联的键将自动删除。注册表中的所有键比较都是使用匹配操作 (===/2) 进行的。

注册表可用于不同的目的,例如名称查找(使用 :via 选项)、存储属性、自定义调度规则或发布订阅实现。我们将在下面探讨其中一些用例。

注册表还可以透明地进行分区,这为在具有数千或数百万条条目的高并发环境中运行注册表提供了更可扩展的行为。

:via 中使用

一旦使用 Registry.start_link/1 启动了具有给定名称的注册表,就可以使用 {:via, Registry, {registry, key}} 元组注册和访问命名进程

{:ok, _} = Registry.start_link(keys: :unique, name: MyApp.Registry)
name = {:via, Registry, {MyApp.Registry, "agent"}}
{:ok, _} = Agent.start_link(fn -> 0 end, name: name)
Agent.get(name, & &1)
#=> 0
Agent.update(name, &(&1 + 1))
Agent.get(name, & &1)
#=> 1

在前面的示例中,我们对将值与进程关联不感兴趣

Registry.lookup(MyApp.Registry, "agent")
#=> [{self(), nil}]

但是,在某些情况下,可能希望使用备用 {:via, Registry, {registry, key, value}} 元组将值与进程关联

{:ok, _} = Registry.start_link(keys: :unique, name: MyApp.Registry)
name = {:via, Registry, {MyApp.Registry, "agent", :hello}}
{:ok, agent_pid} = Agent.start_link(fn -> 0 end, name: name)
Registry.lookup(MyApp.Registry, "agent")
#=> [{agent_pid, :hello}]

到目前为止,我们一直在使用 start_link/1 启动 Registry。通常,注册表是在监督树中的一部分启动的

{Registry, keys: :unique, name: MyApp.Registry}

只有具有唯一键的注册表才能在 :via 中使用。如果名称已被占用,则特定于情况的 start_link 函数(如上例中的 Agent.start_link/2)将返回 {:error, {:already_started, current_pid}}

用作调度器

Registry 具有调度机制,允许开发人员实现从调用者触发的自定义调度逻辑。例如,假设我们有一个按如下方式启动的重复注册表

{:ok, _} = Registry.start_link(keys: :duplicate, name: Registry.DispatcherTest)

通过调用 register/3,不同的进程可以在给定键下注册,并在该键下关联任何值。在本例中,让我们在键 "hello" 下注册当前进程,并将 {IO, :inspect} 元组附加到它

{:ok, _} = Registry.register(Registry.DispatcherTest, "hello", {IO, :inspect})

现在,有兴趣为给定键调度事件的实体可以调用 dispatch/3,传入键和回调函数。此回调将使用注册在请求的键下所有值的列表以及注册每个值的进程的 PID(以 {pid, value} 元组的形式)调用。在我们的示例中,value 将是上面代码中的 {module, function} 元组

Registry.dispatch(Registry.DispatcherTest, "hello", fn entries ->
  for {pid, {module, function}} <- entries, do: apply(module, function, [pid])
end)
# Prints #PID<...> where the PID is for the process that called register/3 above
#=> :ok

调度发生在调用 dispatch/3 的进程中,无论是在多个分区的情况下(通过生成的 task)按顺序进行还是并发进行。注册的进程不会参与调度,除非明确地让它们参与(例如,通过向它们发送回调中的消息)。

此外,如果在调度时由于注册错误而发生错误,调度将始终失败,并且不会通知注册的进程。因此,让我们确保至少包装并报告这些错误

require Logger

Registry.dispatch(Registry.DispatcherTest, "hello", fn entries ->
  for {pid, {module, function}} <- entries do
    try do
      apply(module, function, [pid])
    catch
      kind, reason ->
        formatted = Exception.format(kind, reason, __STACKTRACE__)
        Logger.error("Registry.dispatch/3 failed with #{formatted}")
    end
  end
end)
# Prints #PID<...>
#=> :ok

您还可以通过显式发送消息来替换整个 apply 系统。这就是我们将在下一步中看到的示例。

用作发布订阅系统

注册表还可以通过依赖于 dispatch/3 函数来实现本地、非分布式、可扩展的发布订阅系统,类似于上一节:但是,在本例中,我们将向每个关联的进程发送消息,而不是调用给定的模块-函数。

在本例中,我们还将分区数设置为在线调度的数量,这将在高并发环境中使注册表性能更高

{:ok, _} =
  Registry.start_link(
    keys: :duplicate,
    name: Registry.PubSubTest,
    partitions: System.schedulers_online()
  )

{:ok, _} = Registry.register(Registry.PubSubTest, "hello", [])

Registry.dispatch(Registry.PubSubTest, "hello", fn entries ->
  for {pid, _} <- entries, do: send(pid, {:broadcast, "world"})
end)
#=> :ok

上面的示例将消息 {:broadcast, "world"} 广播到在 "topic"(或我们之前称为 "key")"hello" 下注册的所有进程。

传递给 register/3 的第三个参数是与当前进程关联的值。虽然在上一节中我们在调度时使用了它,但在本例中我们对此不感兴趣,因此我们将其设置为空列表。如果需要,您可以存储更具意义的值。

注册

查找、调度和注册非常有效且即时,但取消订阅会延迟。例如,如果某个进程崩溃,其键将自动从注册表中删除,但更改可能不会立即传播。这意味着某些操作可能会返回已死掉的进程。如果可能发生这种情况,将在函数文档中明确说明。

但是,请记住,这些情况通常不是问题。毕竟,通过 PID 引用的进程可能在任何时候崩溃,包括在从注册表获取值并向其发送消息之间。标准库的许多部分都设计为应对这种情况,例如 Process.monitor/1,如果监视的进程已死,它将立即传递 :DOWN 消息,而 send/2 对于死掉的进程将作为空操作。

ETS

请注意,注册表使用一个 ETS 表以及每个分区两个 ETS 表。

摘要

类型

用于表示匹配规范输出格式部分的模式

在注册表中匹配对象时要评估的守卫

在注册表中匹配对象时要评估的守卫列表

注册时允许的键类型

注册表类型

当进程注册或取消注册时,注册表发送给侦听器的消息。

与注册表中的对象匹配的模式

注册表元数据键类型

注册表元数据值类型

注册表标识符

在注册表中选择对象时使用的完整匹配规范

注册时允许的值类型

函数

返回在主管下启动注册表的规范。

返回注册表中注册键的数量。它在恒定时间内运行。

返回在给定 registry 中给定 key 下匹配 pattern{pid, value} 对的数量。

select/2 类似,但只返回匹配记录的数量。

删除给定 registry 中给定 key 的注册表元数据。

使用给定 registry 中每个分区中 key 下的所有条目调用回调。

以无特定顺序返回给定 registry 中给定 pid 的已知键。

以无特定顺序查找给定 registry 中给定 key{pid, value} 对。

返回给定 registry 中给定 key 下匹配 pattern{pid, value} 对。

读取在 start_link/1 上给出的注册表元数据。

存储注册表元数据。

在给定 registry 中给定 key 下注册当前进程。

使用完整匹配规范选择使用注册的键、PID 和值。

将注册表作为主管进程启动。

取消注册给定 registry 中与当前进程关联的给定 key 的所有条目。

取消注册与给定 registry 中当前进程关联的与模式匹配的键的条目。

更新唯一 registry 中当前进程的 key 的值。

读取给定 registry 中给定 pid 的给定 key 的值。

类型

@type body() :: [term()]

用于表示匹配规范输出格式部分的模式

@type guard() :: atom() | tuple()

在注册表中匹配对象时要评估的守卫

@type guards() :: [guard()]

在注册表中匹配对象时要评估的守卫列表

@type key() :: term()

注册时允许的键类型

@type keys() :: :unique | :duplicate

注册表类型

链接到此类型

listener_message()

查看源代码 (自 1.15.0 起)
@type listener_message() ::
  {:register, registry(), key(), registry_partition :: pid(), value()}
  | {:unregister, registry(), key(), registry_partition :: pid()}

当进程注册或取消注册时,注册表发送给侦听器的消息。

参见 start_link/1 中的 :listeners 选项。

@type match_pattern() :: atom() | term()

与注册表中的对象匹配的模式

@type meta_key() :: atom() | tuple()

注册表元数据键类型

@type meta_value() :: term()

注册表元数据值类型

@type registry() :: atom()

注册表标识符

@type spec() :: [{match_pattern(), guards(), body()}]

在注册表中选择对象时使用的完整匹配规范

@type start_option() ::
  {:keys, keys()}
  | {:name, registry()}
  | {:partitions, pos_integer()}
  | {:listeners, [atom()]}
  | {:meta, [{meta_key(), meta_value()}]}

用于 child_spec/1start_link/1 的选项

@type value() :: term()

注册时允许的值类型

函数

链接到此函数

child_spec(options)

查看源代码 (自 1.5.0 起)
@spec child_spec([start_option()]) :: Supervisor.child_spec()

返回在主管下启动注册表的规范。

参见 Supervisor

链接到此函数

count(registry)

查看源代码 (自 1.7.0 起)
@spec count(registry()) :: non_neg_integer()

返回注册表中注册键的数量。它在恒定时间内运行。

示例

在下面的示例中,我们注册了当前进程,并询问了注册表中的键数量

iex> Registry.start_link(keys: :unique, name: Registry.UniqueCountTest)
iex> Registry.count(Registry.UniqueCountTest)
0
iex> {:ok, _} = Registry.register(Registry.UniqueCountTest, "hello", :world)
iex> {:ok, _} = Registry.register(Registry.UniqueCountTest, "world", :world)
iex> Registry.count(Registry.UniqueCountTest)
2

重复注册表也适用

iex> Registry.start_link(keys: :duplicate, name: Registry.DuplicateCountTest)
iex> Registry.count(Registry.DuplicateCountTest)
0
iex> {:ok, _} = Registry.register(Registry.DuplicateCountTest, "hello", :world)
iex> {:ok, _} = Registry.register(Registry.DuplicateCountTest, "hello", :world)
iex> Registry.count(Registry.DuplicateCountTest)
2
链接到此函数

count_match(registry, key, pattern, guards \\ [])

查看源代码 (自 1.7.0 起)
@spec count_match(registry(), key(), match_pattern(), guards()) :: non_neg_integer()

返回在给定 registry 中给定 key 下匹配 pattern{pid, value} 对的数量。

模式必须是一个原子或一个元组,它将匹配存储在注册表中的值的结构。原子 :_ 可用于忽略给定的值或元组元素,而原子 :"$1" 可用于将模式的一部分临时分配给变量以进行后续比较。

可以选择,可以传递一个守卫条件列表以进行更精确的匹配。每个守卫是一个元组,它描述了分配的模式部分应该通过的检查。例如,$1 > 1 守卫条件将表示为 {:>, :"$1", 1} 元组。请注意,守卫条件仅适用于像 :"$1":"$2" 等分配的变量。避免使用特殊匹配变量 :"$_":"$$",因为它们可能无法按预期工作。

如果没有匹配,将返回 0。

对于唯一的注册表,只需要执行一次分区查找。对于重复的注册表,必须查找所有分区。

示例

在下面的示例中,我们在重复的注册表中使用相同的键注册当前进程,但使用不同的值

iex> Registry.start_link(keys: :duplicate, name: Registry.CountMatchTest)
iex> {:ok, _} = Registry.register(Registry.CountMatchTest, "hello", {1, :atom, 1})
iex> {:ok, _} = Registry.register(Registry.CountMatchTest, "hello", {2, :atom, 2})
iex> Registry.count_match(Registry.CountMatchTest, "hello", {1, :_, :_})
1
iex> Registry.count_match(Registry.CountMatchTest, "hello", {2, :_, :_})
1
iex> Registry.count_match(Registry.CountMatchTest, "hello", {:_, :atom, :_})
2
iex> Registry.count_match(Registry.CountMatchTest, "hello", {:"$1", :_, :"$1"})
2
iex> Registry.count_match(Registry.CountMatchTest, "hello", {:_, :_, :"$1"}, [{:>, :"$1", 1}])
1
iex> Registry.count_match(Registry.CountMatchTest, "hello", {:_, :"$1", :_}, [{:is_atom, :"$1"}])
2
链接到此函数

count_select(registry, spec)

查看源代码 (自 1.14.0 起)
@spec count_select(registry(), spec()) :: non_neg_integer()

select/2 类似,但只返回匹配记录的数量。

示例

在下面的示例中,我们在唯一的注册表中使用不同的键注册当前进程,但使用相同的值

iex> Registry.start_link(keys: :unique, name: Registry.CountSelectTest)
iex> {:ok, _} = Registry.register(Registry.CountSelectTest, "hello", :value)
iex> {:ok, _} = Registry.register(Registry.CountSelectTest, "world", :value)
iex> Registry.count_select(Registry.CountSelectTest, [{{:_, :_, :value}, [], [true]}])
2
链接到此函数

delete_meta(registry, key)

查看源代码 (自 1.11.0 起)
@spec delete_meta(registry(), meta_key()) :: :ok

删除给定 registry 中给定 key 的注册表元数据。

示例

iex> Registry.start_link(keys: :unique, name: Registry.DeleteMetaTest)
iex> Registry.put_meta(Registry.DeleteMetaTest, :custom_key, "custom_value")
:ok
iex> Registry.meta(Registry.DeleteMetaTest, :custom_key)
{:ok, "custom_value"}
iex> Registry.delete_meta(Registry.DeleteMetaTest, :custom_key)
:ok
iex> Registry.meta(Registry.DeleteMetaTest, :custom_key)
:error
链接到此函数

dispatch(registry, key, mfa_or_fun, opts \\ [])

查看源代码 (自 1.4.0 起)
@spec dispatch(registry(), key(), dispatcher, keyword()) :: :ok
when dispatcher:
       (entries :: [{pid(), value()}] -> term()) | {module(), atom(), [any()]}

使用给定 registry 中每个分区中 key 下的所有条目调用回调。

entries 列表是一个非空列表,包含两个元素的元组,其中第一个元素是 PID,第二个元素是与 PID 关联的值。如果给定键没有条目,则回调永远不会被调用。

如果注册表被分区,则每个分区都会调用回调多次。如果注册表被分区并且 parallel: true 被指定为选项,则调度会并行进行。在这两种情况下,回调仅在该分区有条目时才会被调用。

有关使用 dispatch/3 函数构建自定义调度或发布/订阅系统的示例,请参见模块文档。

链接到此函数

keys(registry, pid)

查看源代码 (自 1.4.0 起)
@spec keys(registry(), pid()) :: [key()]

以无特定顺序返回给定 registry 中给定 pid 的已知键。

如果注册表是唯一的,则键是唯一的。否则,如果进程在同一个键下注册多次,则键可能包含重复项。如果进程已死或在这个注册表中没有键,则列表将为空。

示例

在唯一注册表下注册不允许有多个条目

iex> Registry.start_link(keys: :unique, name: Registry.UniqueKeysTest)
iex> Registry.keys(Registry.UniqueKeysTest, self())
[]
iex> {:ok, _} = Registry.register(Registry.UniqueKeysTest, "hello", :world)
iex> Registry.register(Registry.UniqueKeysTest, "hello", :later) # registry is :unique
{:error, {:already_registered, self()}}
iex> Registry.keys(Registry.UniqueKeysTest, self())
["hello"]

对于重复的注册表,这是可能的

iex> Registry.start_link(keys: :duplicate, name: Registry.DuplicateKeysTest)
iex> Registry.keys(Registry.DuplicateKeysTest, self())
[]
iex> {:ok, _} = Registry.register(Registry.DuplicateKeysTest, "hello", :world)
iex> {:ok, _} = Registry.register(Registry.DuplicateKeysTest, "hello", :world)
iex> Registry.keys(Registry.DuplicateKeysTest, self())
["hello", "hello"]
链接到此函数

lookup(registry, key)

查看源代码 (自 1.4.0 起)
@spec lookup(registry(), key()) :: [{pid(), value()}]

以无特定顺序查找给定 registry 中给定 key{pid, value} 对。

如果没有任何匹配,则是一个空列表。

对于唯一的注册表,只需要执行一次分区查找。对于重复的注册表,必须查找所有分区。

示例

在下面的示例中,我们注册当前进程并从自身和其他进程中查找它

iex> Registry.start_link(keys: :unique, name: Registry.UniqueLookupTest)
iex> Registry.lookup(Registry.UniqueLookupTest, "hello")
[]
iex> {:ok, _} = Registry.register(Registry.UniqueLookupTest, "hello", :world)
iex> Registry.lookup(Registry.UniqueLookupTest, "hello")
[{self(), :world}]
iex> Task.async(fn -> Registry.lookup(Registry.UniqueLookupTest, "hello") end) |> Task.await()
[{self(), :world}]

重复注册表也适用

iex> Registry.start_link(keys: :duplicate, name: Registry.DuplicateLookupTest)
iex> Registry.lookup(Registry.DuplicateLookupTest, "hello")
[]
iex> {:ok, _} = Registry.register(Registry.DuplicateLookupTest, "hello", :world)
iex> Registry.lookup(Registry.DuplicateLookupTest, "hello")
[{self(), :world}]
iex> {:ok, _} = Registry.register(Registry.DuplicateLookupTest, "hello", :another)
iex> Enum.sort(Registry.lookup(Registry.DuplicateLookupTest, "hello"))
[{self(), :another}, {self(), :world}]
链接到此函数

match(registry, key, pattern, guards \\ [])

查看源代码 (自 1.4.0 起)
@spec match(registry(), key(), match_pattern(), guards()) :: [{pid(), term()}]

返回给定 registry 中给定 key 下匹配 pattern{pid, value} 对。

模式必须是一个原子或一个元组,它将匹配存储在注册表中的值的结构。原子 :_ 可用于忽略给定的值或元组元素,而原子 :"$1" 可用于将模式的一部分临时分配给变量以进行后续比较。

可以选择,可以传递一个守卫条件列表以进行更精确的匹配。每个守卫是一个元组,它描述了分配的模式部分应该通过的检查。例如,$1 > 1 守卫条件将表示为 {:>, :"$1", 1} 元组。请注意,守卫条件仅适用于像 :"$1":"$2" 等分配的变量。避免使用特殊匹配变量 :"$_":"$$",因为它们可能无法按预期工作。

如果没有任何匹配,则将返回一个空列表。

对于唯一的注册表,只需要执行一次分区查找。对于重复的注册表,必须查找所有分区。

示例

在下面的示例中,我们在重复的注册表中使用相同的键注册当前进程,但使用不同的值

iex> Registry.start_link(keys: :duplicate, name: Registry.MatchTest)
iex> {:ok, _} = Registry.register(Registry.MatchTest, "hello", {1, :atom, 1})
iex> {:ok, _} = Registry.register(Registry.MatchTest, "hello", {2, :atom, 2})
iex> Registry.match(Registry.MatchTest, "hello", {1, :_, :_})
[{self(), {1, :atom, 1}}]
iex> Registry.match(Registry.MatchTest, "hello", {2, :_, :_})
[{self(), {2, :atom, 2}}]
iex> Registry.match(Registry.MatchTest, "hello", {:_, :atom, :_}) |> Enum.sort()
[{self(), {1, :atom, 1}}, {self(), {2, :atom, 2}}]
iex> Registry.match(Registry.MatchTest, "hello", {:"$1", :_, :"$1"}) |> Enum.sort()
[{self(), {1, :atom, 1}}, {self(), {2, :atom, 2}}]
iex> guards = [{:>, :"$1", 1}]
iex> Registry.match(Registry.MatchTest, "hello", {:_, :_, :"$1"}, guards)
[{self(), {2, :atom, 2}}]
iex> guards = [{:is_atom, :"$1"}]
iex> Registry.match(Registry.MatchTest, "hello", {:_, :"$1", :_}, guards) |> Enum.sort()
[{self(), {1, :atom, 1}}, {self(), {2, :atom, 2}}]
链接到此函数

meta(registry, key)

查看源代码 (自 1.4.0 起)
@spec meta(registry(), meta_key()) :: {:ok, meta_value()} | :error

读取在 start_link/1 上给出的注册表元数据。

原子和元组允许作为键。

示例

iex> Registry.start_link(keys: :unique, name: Registry.MetaTest, meta: [custom_key: "custom_value"])
iex> Registry.meta(Registry.MetaTest, :custom_key)
{:ok, "custom_value"}
iex> Registry.meta(Registry.MetaTest, :unknown_key)
:error
链接到此函数

put_meta(registry, key, value)

查看源代码 (自 1.4.0 起)
@spec put_meta(registry(), meta_key(), meta_value()) :: :ok

存储注册表元数据。

原子和元组允许作为键。

示例

iex> Registry.start_link(keys: :unique, name: Registry.PutMetaTest)
iex> Registry.put_meta(Registry.PutMetaTest, :custom_key, "custom_value")
:ok
iex> Registry.meta(Registry.PutMetaTest, :custom_key)
{:ok, "custom_value"}
iex> Registry.put_meta(Registry.PutMetaTest, {:tuple, :key}, "tuple_value")
:ok
iex> Registry.meta(Registry.PutMetaTest, {:tuple, :key})
{:ok, "tuple_value"}
链接到此函数

register(registry, key, value)

查看源代码 (自 1.4.0 起)
@spec register(registry(), key(), value()) ::
  {:ok, pid()} | {:error, {:already_registered, pid()}}

在给定 registry 中给定 key 下注册当前进程。

还必须提供与该注册关联的值。这个值将在调度或进行键查找时被检索。

此函数返回 {:ok, owner}{:error, reason}owner 是注册表分区中负责 PID 的 PID。owner 会自动链接到调用者。

如果注册表具有唯一的键,它将返回 {:ok, owner},除非该键已经与 PID 关联,在这种情况下它将返回 {:error, {:already_registered, pid}}

如果注册表具有重复的键,则允许当前进程在同一个键下进行多次注册。

如果注册表具有通过 start_link/1 中的 :listeners 选项指定的监听器,那么这些监听器将被通知注册,并将收到类型为 listener_message/0 的消息。

示例

在唯一注册表下注册不允许有多个条目

iex> Registry.start_link(keys: :unique, name: Registry.UniqueRegisterTest)
iex> {:ok, _} = Registry.register(Registry.UniqueRegisterTest, "hello", :world)
iex> Registry.register(Registry.UniqueRegisterTest, "hello", :later)
{:error, {:already_registered, self()}}
iex> Registry.keys(Registry.UniqueRegisterTest, self())
["hello"]

对于重复的注册表,这是可能的

iex> Registry.start_link(keys: :duplicate, name: Registry.DuplicateRegisterTest)
iex> {:ok, _} = Registry.register(Registry.DuplicateRegisterTest, "hello", :world)
iex> {:ok, _} = Registry.register(Registry.DuplicateRegisterTest, "hello", :world)
iex> Registry.keys(Registry.DuplicateRegisterTest, self())
["hello", "hello"]
链接到此函数

select(registry, spec)

查看源代码 (自 1.9.0 起)
@spec select(registry(), spec()) :: [term()]

使用完整匹配规范选择使用注册的键、PID 和值。

spec 包含一个三部分元组列表,形状为 [{match_pattern, guards, body}]

第一部分,匹配模式,必须是一个元组,它将匹配存储在注册表中的数据的结构,即 {key, pid, value}。原子 :_ 可用于忽略给定的值或元组元素,而原子 :"$1" 可用于将模式的一部分临时分配给变量以进行后续比较。这可以像 {:"$1", :_, :_} 那样组合使用。

第二部分,守卫,是一个允许过滤结果的条件列表。每个守卫是一个元组,它描述了分配的模式部分应该通过的检查。例如,$1 > 1 守卫条件将表示为 {:>, :"$1", 1} 元组。请注意,守卫条件仅适用于像 :"$1":"$2" 等分配的变量。

第三部分,主体,是返回条目的形状列表。像守卫一样,您可以访问像 :"$1" 这样的分配变量,您可以将其与硬编码值结合起来,以自由地塑造条目。请注意,元组必须用一个额外的元组包装。为了获得像 %{key: key, pid: pid, value: value} 这样的结果格式,假设您在匹配部分按顺序绑定了这些变量,您将提供一个主体,例如 [%{key: :"$1", pid: :"$2", value: :"$3"}]。像守卫一样,您可以使用一些操作,例如 :element 来修改输出格式。

不要使用特殊匹配变量 :"$_":"$$",因为它们可能无法按预期工作。

请注意,对于具有许多分区的庞大注册表,这将很昂贵,因为它通过连接所有分区来构建结果。

示例

此示例展示了如何从注册表中获取所有内容

iex> Registry.start_link(keys: :unique, name: Registry.SelectAllTest)
iex> {:ok, _} = Registry.register(Registry.SelectAllTest, "hello", :value)
iex> {:ok, _} = Registry.register(Registry.SelectAllTest, "world", :value)
iex> Registry.select(Registry.SelectAllTest, [{{:"$1", :"$2", :"$3"}, [], [{{:"$1", :"$2", :"$3"}}]}]) |> Enum.sort()
[{"hello", self(), :value}, {"world", self(), :value}]

获取注册表中的所有键

iex> Registry.start_link(keys: :unique, name: Registry.SelectAllTest)
iex> {:ok, _} = Registry.register(Registry.SelectAllTest, "hello", :value)
iex> {:ok, _} = Registry.register(Registry.SelectAllTest, "world", :value)
iex> Registry.select(Registry.SelectAllTest, [{{:"$1", :_, :_}, [], [:"$1"]}]) |> Enum.sort()
["hello", "world"]
链接到此函数

start_link(options)

查看源代码 (自 1.5.0 起)
@spec start_link([start_option()]) :: {:ok, pid()} | {:error, term()}

将注册表作为主管进程启动。

它可以手动启动为

Registry.start_link(keys: :unique, name: MyApp.Registry)

在您的监管者树中,您将编写

Supervisor.start_link([
  {Registry, keys: :unique, name: MyApp.Registry}
], strategy: :one_for_one)

对于密集的工作负载,注册表也可以被分区(通过指定 :partitions 选项)。如果需要分区,那么一个好的默认值是将分区数量设置为可用的调度器数量

Registry.start_link(
  keys: :unique,
  name: MyApp.Registry,
  partitions: System.schedulers_online()
)

Supervisor.start_link([
  {Registry, keys: :unique, name: MyApp.Registry, partitions: System.schedulers_online()}
], strategy: :one_for_one)

选项

注册表需要以下键

  • :keys - 选择键是 :unique 还是 :duplicate
  • :name - 注册表及其表的名称

以下键是可选的

  • :partitions - 注册表中的分区数量。默认为 1
  • :listeners - 一个命名进程列表,它们会收到注册和注销事件的通知。如果监听器想要在注册进程崩溃时收到通知,则注册进程必须被监听器监视。发送给监听器者的消息类型为 listener_message/0
  • :meta - 附加到注册表的元数据关键字列表。
链接到此函数

unregister(registry, key)

查看源代码 (自 1.4.0 起)
@spec unregister(registry(), key()) :: :ok

取消注册给定 registry 中与当前进程关联的给定 key 的所有条目。

始终返回 :ok,如果当前进程没有与之关联的键,则自动从 owner 解除当前进程的链接。有关“owner”的更多信息,请参见 register/3

如果注册表具有通过 start_link/1 中的 :listeners 选项指定的监听器,那么这些监听器将被通知注销,并将收到类型为 listener_message/0 的消息。

示例

对于唯一的注册表

iex> Registry.start_link(keys: :unique, name: Registry.UniqueUnregisterTest)
iex> Registry.register(Registry.UniqueUnregisterTest, "hello", :world)
iex> Registry.keys(Registry.UniqueUnregisterTest, self())
["hello"]
iex> Registry.unregister(Registry.UniqueUnregisterTest, "hello")
:ok
iex> Registry.keys(Registry.UniqueUnregisterTest, self())
[]

对于重复的注册表

iex> Registry.start_link(keys: :duplicate, name: Registry.DuplicateUnregisterTest)
iex> Registry.register(Registry.DuplicateUnregisterTest, "hello", :world)
iex> Registry.register(Registry.DuplicateUnregisterTest, "hello", :world)
iex> Registry.keys(Registry.DuplicateUnregisterTest, self())
["hello", "hello"]
iex> Registry.unregister(Registry.DuplicateUnregisterTest, "hello")
:ok
iex> Registry.keys(Registry.DuplicateUnregisterTest, self())
[]
链接到此函数

unregister_match(registry, key, pattern, guards \\ [])

查看源代码 (自 1.5.0 起)
@spec unregister_match(registry(), key(), match_pattern(), guards()) :: :ok

取消注册与给定 registry 中当前进程关联的与模式匹配的键的条目。

示例

对于唯一的注册表,它可以用来根据键是否匹配特定值来有条件地注销键。

iex> Registry.start_link(keys: :unique, name: Registry.UniqueUnregisterMatchTest)
iex> Registry.register(Registry.UniqueUnregisterMatchTest, "hello", :world)
iex> Registry.keys(Registry.UniqueUnregisterMatchTest, self())
["hello"]
iex> Registry.unregister_match(Registry.UniqueUnregisterMatchTest, "hello", :foo)
:ok
iex> Registry.keys(Registry.UniqueUnregisterMatchTest, self())
["hello"]
iex> Registry.unregister_match(Registry.UniqueUnregisterMatchTest, "hello", :world)
:ok
iex> Registry.keys(Registry.UniqueUnregisterMatchTest, self())
[]

对于重复的注册表

iex> Registry.start_link(keys: :duplicate, name: Registry.DuplicateUnregisterMatchTest)
iex> Registry.register(Registry.DuplicateUnregisterMatchTest, "hello", :world_a)
iex> Registry.register(Registry.DuplicateUnregisterMatchTest, "hello", :world_b)
iex> Registry.register(Registry.DuplicateUnregisterMatchTest, "hello", :world_c)
iex> Registry.keys(Registry.DuplicateUnregisterMatchTest, self())
["hello", "hello", "hello"]
iex> Registry.unregister_match(Registry.DuplicateUnregisterMatchTest, "hello", :world_a)
:ok
iex> Registry.keys(Registry.DuplicateUnregisterMatchTest, self())
["hello", "hello"]
iex> Registry.lookup(Registry.DuplicateUnregisterMatchTest, "hello")
[{self(), :world_b}, {self(), :world_c}]
链接到此函数

update_value(registry, key, callback)

查看源代码 (自 1.4.0 起)
@spec update_value(registry(), key(), (value() -> value())) ::
  {new_value :: term(), old_value :: term()} | :error

更新唯一 registry 中当前进程的 key 的值。

返回一个 {new_value, old_value} 元组,如果当前进程没有分配给该键,则返回 :error

如果给定一个非唯一的注册表,则会引发错误。

示例

iex> Registry.start_link(keys: :unique, name: Registry.UpdateTest)
iex> {:ok, _} = Registry.register(Registry.UpdateTest, "hello", 1)
iex> Registry.lookup(Registry.UpdateTest, "hello")
[{self(), 1}]
iex> Registry.update_value(Registry.UpdateTest, "hello", &(&1 + 1))
{2, 1}
iex> Registry.lookup(Registry.UpdateTest, "hello")
[{self(), 2}]
链接到此函数

values(registry, key, pid)

查看源代码 (自 1.12.0 起)
@spec values(registry(), key(), pid()) :: [value()]

读取给定 registry 中给定 pid 的给定 key 的值。

对于唯一注册表,它要么是空列表,要么是只有一个元素的列表。对于重复注册表,它是一个包含零个、一个或多个元素的列表。

示例

在下面的示例中,我们注册当前进程并从自身和其他进程中查找它

iex> Registry.start_link(keys: :unique, name: Registry.UniqueLookupTest)
iex> Registry.values(Registry.UniqueLookupTest, "hello", self())
[]
iex> {:ok, _} = Registry.register(Registry.UniqueLookupTest, "hello", :world)
iex> Registry.values(Registry.UniqueLookupTest, "hello", self())
[:world]
iex> Task.async(fn -> Registry.values(Registry.UniqueLookupTest, "hello", self()) end) |> Task.await()
[]
iex> parent = self()
iex> Task.async(fn -> Registry.values(Registry.UniqueLookupTest, "hello", parent) end) |> Task.await()
[:world]

重复注册表也适用

iex> Registry.start_link(keys: :duplicate, name: Registry.DuplicateLookupTest)
iex> Registry.values(Registry.DuplicateLookupTest, "hello", self())
[]
iex> {:ok, _} = Registry.register(Registry.DuplicateLookupTest, "hello", :world)
iex> Registry.values(Registry.DuplicateLookupTest, "hello", self())
[:world]
iex> {:ok, _} = Registry.register(Registry.DuplicateLookupTest, "hello", :another)
iex> Enum.sort(Registry.values(Registry.DuplicateLookupTest, "hello", self()))
[:another, :world]