Spec-Zone.ru › Elixir 1.15

Регистр

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

Позволяет разработчикам искать один или несколько процессов по заданному ключу. Если регистр поддерживает :unique ключи, ключ указывает на 0 или 1 процесс. Если регистр поддерживает :duplicate ключи, один ключ может указывать на любое количество процессов. В обоих случаях разные ключи могут идентифицировать один и тот же процесс.

Каждая запись в регистре ассоциирована с процессом, который зарегистрировал ключ. Если процесс завершается, ключи, связанные с этим процессом, автоматически удаляются. Все сравнения ключей в регистре выполняются с помощью операции совпадения (===/2).

Регистр может использоваться для различных целей, таких как поиск по именам (используя :via опцию), хранение свойств, пользовательские правила диспетчеризации или реализация pubsub. Ниже мы рассмотрим некоторые из этих вариантов использования.

Регистр также может быть прозрачно разбит на разделы, что обеспечивает более масштабируемое поведение при выполнении регистров в высококонкурентных средах с тысячами или миллионами записей.

Использование в :via

После запуска регистра с заданным именем с помощью Registry.start_link/1, можно использовать его для регистрации и доступа к именованным процессам, используя кортеж {:via, Registry, {registry, key}}:

{:ok, _} = Registry.start_link(keys: :unique, name: Registry.ViaTest)
name = {:via, Registry, {Registry.ViaTest, "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(Registry.ViaTest, "agent")
#=> [{self(), nil}]

Однако в некоторых случаях может потребоваться привязать значение к процессу, используя альтернативный кортеж {:via, Registry, {registry, key, value}}:

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

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

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

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

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"}}]}])
[{"world", self(), :value}, {"hello", 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"]}])
["world", "hello"]

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]

© 2012 Plataformatec
Licensed under the Apache License, Version 2.0.
https://hexdocs.pm/elixir/1.15.4/Registry.html

Spec-Zone.ru

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