Spec-Zone.ru › Elixir 1.16

Источник Реестр

Локальное, децентрализованное и масштабируемое хранилище процессов ключ-значение.

Он позволяет разработчикам искать один или несколько процессов с заданным ключом. Если реестр имеет :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}]

До этого момента мы запускали Registry с помощью start_link/1. Однако, обычно реестр запускается как часть дерева надзора:

{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, либо последовательно, либо параллельно в случае нескольких разделов (через запущенные задачи). Зарегистрированные процессы не участвуют в маршрутизации, если это не сделано явно (например, отправкой им сообщения в обратном вызове).

Кроме того, если при маршрутизации произойдёт ошибка из-за неправильной регистрации, маршрутизация всегда завершится ошибкой, и зарегистрированный процесс не будет уведомлён. Поэтому давайте позаботимся о том, чтобы мы по крайней мере обернули и сообщили об этих ошибках:

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 явным отправлением сообщений. Это пример, который мы увидим дальше.

Использование в качестве PubSub

Реестры также могут быть использованы для реализации локальной, нераспределённой, масштабируемой системы публикации-подписки, опираясь на функцию 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"} было транслировано всем процессам, зарегистрированным под «темой» (или «ключом», как мы его называли до сих пор) "hello".

Третьим аргументом, передаваемым в register/3, является значение, связанное с текущим процессом. В то время как в предыдущем разделе мы использовали его при маршрутизации, в этом конкретном примере мы в нём не заинтересованы, поэтому мы установили его в пустой список. При необходимости вы можете сохранить более значимое значение.

Регистрации

Поиск, маршрутизация и регистрация являются эффективными и мгновенными за счёт задержки отписки. Например, если процесс завершается, его ключи автоматически удаляются из реестра, но изменение может не распространиться сразу. Это означает, что некоторые операции могут возвращать процессы, которые уже завершены. Когда такое может произойти, это будет чётко указано в документации функции.

Однако имейте в виду, что такие случаи обычно не являются проблемой. В конце концов, процесс, на который ссылается PID, может завершиться в любое время, включая период между получением значения из реестра и отправкой ему сообщения. Многие части стандартной библиотеки разработаны для работы с этим, например, Process.monitor/1, который передаст сообщение :DOWN мгновенно, если отслеживаемый процесс уже завершен, и send/2, который действует как бесполезная операция для завершённых процессов.

ETS

Обратите внимание, что реестр использует одну таблицу ETS плюс две таблицы ETS на каждый раздел.

Краткое описание

Типы

body()

Шаблон, используемый для представления части формата вывода спецификации соответствия

guard()

Фильтр, который нужно оценить при сопоставлении с объектами в реестре

guards()

Список фильтров, которые необходимо оценить при сопоставлении с объектами в реестре

key()

Тип ключей, разрешенных для регистрации

keys()

Тип реестра

listener_message()

Сообщение, которое реестр отправляет слушателям, когда процесс регистрируется или отменяет регистрацию.

match_pattern()

Шаблон для сопоставления с объектами в реестре

meta_key()

Тип метаданных ключей реестра

meta_value()

Тип метаданных значений реестра

registry()

Идентификатор реестра

spec()

Полная спецификация соответствия, используемая при выборе объектов в реестре

start_option()

Параметры, используемые для child_spec/1 и start_link/1

value()

Тип значений, разрешенных при регистрации

Функции

child_spec(options)

Возвращает спецификацию для запуска реестра под надзором.

count(registry)

Возвращает количество зарегистрированных ключей в реестре. Работает за постоянное время.

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

Возвращает количество пар {pid, value}, находящихся под заданным key, в registry, которые соответствуют pattern.

count_select(registry, spec)

Работает как select/2, но возвращает только количество совпадающих записей.

delete_meta(registry, key)

Удаляет метаданные реестра для заданного key, в registry.

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

Вызывает обратный вызов со всеми записями под key в каждом разделе для заданного registry.

keys(registry, pid)

Возвращает известные ключи для данного pid в registry в произвольном порядке.

lookup(registry, key)

Находит пару {pid, value} для данного key в registry в произвольном порядке.

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

Возвращает {pid, value} пары под заданным key в registry, которые соответствуют pattern.

meta(registry, key)

Читает метаданные реестра, заданные в start_link/1.

put_meta(registry, key, value)

Сохраняет метаданные реестра.

register(registry, key, value)

Регистрирует текущий процесс под заданным key в registry.

select(registry, spec)

Выбирает ключ, pid и значения, зарегистрированные с помощью полных спецификаций соответствия.

start_link(options)

Запускает реестр как процесс-надзорщик.

unregister(registry, key)

Отменяет регистрацию всех записей для данного key, связанных с текущим процессом в registry.

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

Отменяет регистрацию записей для ключей, соответствующих шаблону, связанных с текущим процессом в registry.

update_value(registry, key, callback)

Обновляет значение для key для текущего процесса в уникальном registry.

values(registry, key, pid)

Считывает значения для данного key для pid в registry.

END_OF_DOCUMENT_MARKER

Типы

body()Source

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

Шаблон, используемый для представления части формата вывода спецификации сопоставления

guard()Source

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

Охранник, подлежащий оценке при сопоставлении с объектами в реестре

guards()Source

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

Список охранников, подлежащих оценке при сопоставлении с объектами в реестре

key()Source

@type key() :: term()

Тип ключей, разрешенных при регистрации

keys()Source

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

Тип реестра

listener_message()Source

@type listener_message() ::
  {:register, registry(), key(), registry_partition :: pid(), value()}
  | {:unregister, registry(), key(), registry_partition :: pid()}

Сообщение, которое реестр отправляет слушателям при регистрации или отмене регистрации процесса.

См. опцию :listeners в start_link/1.

match_pattern()Source

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

Шаблон для сопоставления с объектами в реестре

meta_key()Source

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

Тип ключей метаданных реестра

meta_value()Source

@type meta_value() :: term()

Тип значений метаданных реестра

registry()Source

@type registry() :: atom()

Идентификатор реестра

spec()Source

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

Полная спецификация сопоставления, используемая при выборе объектов в реестре

start_option()Source

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

Опции, используемые для child_spec/1 и start_link/1

value()Source

@type value() :: term()

Тип значений, разрешенных при регистрации

END_OF_DOCUMENT_MARKER

Функции

child_spec(options)Source

@spec child_spec([start_option()]) :: Supervisor.child_spec()

Возвращает спецификацию для запуска реестра под надзором.

См. Supervisor.

count(registry)Source

@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 \\ [])Source

@spec count_match(registry(), key(), match_pattern(), guards()) :: non_neg_integer()

Возвращает количество пар {pid, value} под заданным key в registry, которые соответствуют pattern.

Шаблон должен быть атомом или кортежем, который будет соответствовать структуре значения, хранящегося в реестре. Атом :_ может использоваться для игнорирования заданного значения или элемента кортежа, а атом :"$1" может использоваться для временного назначения части шаблона переменной для последующего сравнения.

По желанию, можно передать список условий сторожей для более точного соответствия. Каждый сторож — это кортеж, который описывает проверки, которые должны быть пройдены назначенной частью шаблона. Например, условие сторожа $1 > 1 выразилось бы как кортеж {:>, :"$1", 1}. Обратите внимание, что условия сторожей будут работать только для назначенных переменных, таких как :"$1", :"$2" и т. д. Избегайте использования специальных переменных сопоставления :"$_" и :"$$", поскольку это может не сработать как ожидается.

Ноль будет возвращен, если совпадений нет.

Для уникальных реестров требуется поиск одного раздела. Для дублирующих реестров необходимо выполнить поиск во всех разделах.

Примеры

В примере ниже мы регистрируем текущий процесс под одним и тем же ключом в дублирующем реестре, но с разными значениями:

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

@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)Source

@spec delete_meta(registry(), meta_key()) :: :ok

Удаляет метаданные реестра для данного key в registry.

Примеры

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 \\ [])Source

@spec dispatch(registry(), key(), dispatcher, keyword()) :: :ok
when dispatcher:
       (entries :: [{pid(), value()}] -> term()) | {module(), atom(), [any()]}

Вызывает обратный вызов со всеми записями под key в каждом разделе для данного registry.

Список entries — это непустой список пар с двумя элементами, где первый элемент — PID, а второй — значение, связанное с PID. Если для данного ключа нет записей, обратный вызов никогда не вызывается.

Если реестр разбит на разделы, обратный вызов вызывается несколько раз в каждом разделе. Если реестр разбит на разделы и parallel: true задано в качестве опции, диспетчеризация происходит параллельно. В обоих случаях обратный вызов вызывается только если в этом разделе есть записи.

См. документацию модуля для примеров использования функции dispatch/3 для построения пользовательской диспетчеризации или системы публикаций-подписок.

keys(registry, pid)Source

@spec keys(registry(), pid()) :: [key()]

Возвращает известные ключи для данного pid в registry в произвольном порядке.

Если реестр уникальный, ключи уникальны. В противном случае они могут содержать дубликаты, если процесс был зарегистрирован под одним и тем же ключом несколько раз. Список будет пустым, если процесс мёртв или у него нет ключей в этом реестре.

Примеры

Регистрация в уникальном реестре не позволяет множественных записей:

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

@spec lookup(registry(), key()) :: [{pid(), value()}]

Находит пару {pid, value} для данного key в registry в произвольном порядке.

Пустой список, если совпадений нет.

Для уникальных реестров требуется поиск одного раздела. Для дублирующих реестров необходимо выполнить поиск во всех разделах.

Примеры

В примере ниже мы регистрируем текущий процесс и ищем его как из себя, так и из других процессов:

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 \\ [])Source

@spec match(registry(), key(), match_pattern(), guards()) :: [{pid(), term()}]

Возвращает пары {pid, value} под заданным key в registry, которые соответствуют pattern.

Шаблон должен быть атомом или кортежем, который будет соответствовать структуре значения, хранящегося в реестре. Атом :_ может использоваться для игнорирования заданного значения или элемента кортежа, а атом :"$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)Source

@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)Source

@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)Source

@spec register(registry(), key(), value()) ::
  {:ok, pid()} | {:error, {:already_registered, pid()}}

Регистрирует текущий процесс под заданным key в registry.

Также необходимо указать значение, которое будет ассоциировано с этой регистрацией. Это значение будет извлечено при диспетчеризации или поиске ключа.

Эта функция возвращает {:ok, owner} или {:error, reason}. owner — PID в разделе реестра, отвечающем за PID. Владелец автоматически подключается к вызывающей стороне.

Если реестр имеет уникальные ключи, он вернёт {:ok, owner}, если ключ ещё не ассоциирован с PID, в противном случае он вернёт {:error, {:already_registered, pid}}.

Если реестр имеет дублирующиеся ключи, несколько регистраций текущего процесса под одним и тем же ключом разрешены.

Если в реестре указаны слушатели через опцию :listeners в start_link/1, эти слушатели будут уведомлены о регистрации и получат сообщение типа 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)Source

@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)Source

@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)Source

@spec unregister(registry(), key()) :: :ok

Отменяет регистрацию всех записей для данного key, связанного с текущим процессом в registry.

Всегда возвращает :ok и автоматически разрывает связь текущего процесса с владельцем, если больше нет ключей, связанных с текущим процессом. См. также register/3, чтобы узнать больше о "владельце".

Если в реестре указаны слушатели через опцию :listeners в start_link/1, эти слушатели будут уведомлены об отмене регистрации и получат сообщение типа 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 \\ [])Source

@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)Source

@spec update_value(registry(), key(), (value() -> value())) ::
  {new_value :: term(), old_value :: term()} | :error

Обновляет значение для key для текущего процесса в уникальном registry.

Возвращает кортеж {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)Source

@spec values(registry(), key(), pid()) :: [value()]

Читает значения для данного key для pid в registry.

Для уникальных реестров это либо пустой список, либо список с одним элементом. Для дублирующих реестров — это список с нулём, одним или несколькими элементами.

Примеры

В примере ниже мы регистрируем текущий процесс и ищем его как из самого себя, так и из других процессов:

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]

Скачать версию ePub

Создано с помощью ExDoc (v0.32.2) для Elixir programming language

© 2012-2024 The Elixir Team
Licensed under the Apache License, Version 2.0.
https://hexdocs.pm/elixir/1.16.3/Registry.html

Spec-Zone.ru

Настройки Оффлайн Что нового Помощь О нас
Spec-Zone .ru
спецификации, руководства, описания, API