Spec-Zone.ru › Elixir 1.17

Исходный код Регистр

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

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

Реестры также могут быть использованы для реализации локальной, нераспределённой и масштабируемой системы 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)

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

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

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

Функции

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.UniqueValuesTest)
iex> Registry.values(Registry.UniqueValuesTest, "hello", self())
[]
iex> {:ok, _} = Registry.register(Registry.UniqueValuesTest, "hello", :world)
iex> Registry.values(Registry.UniqueValuesTest, "hello", self())
[:world]
iex> Task.async(fn -> Registry.values(Registry.UniqueValuesTest, "hello", self()) end) |> Task.await()
[]
iex> parent = self()
iex> Task.async(fn -> Registry.values(Registry.UniqueValuesTest, "hello", parent) end) |> Task.await()
[:world]

То же самое относится и к реестрам с повторяющимися ключами:

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

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

Создано с помощью ExDoc (v0.34.1) для языка программирования Elixir

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

Spec-Zone.ru

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