Spec-Zone.ru › Elixir 1.18

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

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

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

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

lock(registry, lock_key, function)

Блокировка вне очереди данного lock_key на период function.

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

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

Функции

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(), [term()]}

Вызывает обратный вызов со всеми записями под 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"]

lock(registry, lock_key, function)Source

Внеполосная блокировка данного lock_key на время function.

Только одна функция может выполняться под одним и тем же lock_key в данный момент времени. Заданная функция всегда выполняется в процессе вызывающего процесса.

lock_key имеет собственное пространство имен и поэтому не конфликтует и не перекрывается с обычными ключами реестра. Другими словами, блокировка работает вне полосы обычных операций реестра. См. раздел "Примеры использования" ниже.

Блокировка ведет себя одинаково независимо от типа реестра.

Примеры использования

Реестр безопасен и многопоточен по умолчанию. Вы не обязаны использовать эту функцию при взаимодействии с реестром. Кроме того, Registry с :unique ключами уже может действовать как блокировка процесса для любого заданного ключа. Например, можно гарантировать, что только один процесс будет выполняться в данный момент времени для заданного :key следующим образом:

name = {:via, Registry, {MyApp.Registry, :key, :value}}

# Do not attempt to start if we are already running
if pid = GenServer.whereis(name) do
  pid
else
  case GenServer.start_link(__MODULE__, :ok, name: name) do
    {:ok, pid} -> pid
    {:error, {:already_started, pid}} -> pid
  end
end

Блокировка процесса предоставляет достаточную гибкость и изоляцию ошибок и достаточно для большинства случаев.

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

Примеры

iex> Registry.start_link(keys: :unique, name: Registry.LockTest)
iex> Registry.lock(Registry.LockTest, :hello, fn -> :ok end)
:ok
iex> Registry.lock(Registry.LockTest, :world, fn -> self() end)
self()

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.SelectKeysTest)
iex> {:ok, _} = Registry.register(Registry.SelectKeysTest, "hello", :value)
iex> {:ok, _} = Registry.register(Registry.SelectKeysTest, "world", :value)
iex> Registry.select(Registry.SelectKeysTest, [{{:"$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]

Download ePub version

Built using ExDoc (v0.36.1) for the Elixir programming language

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

Spec-Zone.ru

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