Регистр
Локальное, децентрализованное и масштабируемое хранилище пар «ключ-значение» для процессов.
Разработчики могут искать один или несколько процессов по заданному ключу. Если регистр допускает :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.start_link(fn -> 0 end, name: name)
Registry.lookup(Registry.ViaTest, "agent")
#=> [{self(), :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 немедленно, если отслеживаемый процесс уже завершен, и Kernel.send/2, который действует как no-op для мертвых процессов.
ETS
Обратите внимание, что регистр использует одну таблицу ETS плюс две таблицы ETS на раздел.
Краткое описание
Типы
- body()
Шаблон, используемый для представления части формата вывода спецификации соответствия.
- guard()
Фильтр, который оценивается при сопоставлении объектов в реестре.
- guards()
Список фильтров, которые оцениваются при сопоставлении объектов в реестре.
- key()
Тип ключей, разрешённых для регистрации.
- keys()
Тип реестра.
- 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.- delete_meta(registry, key)
Удаляет метаданные реестра для заданного
keyвregistry.- dispatch(registry, key, mfa_or_fun, opts \\ [])
Вызывает обратный вызов со всеми записями под
keyв каждом разделе для заданногоregistry.- keys(registry, pid)
Возвращает известные ключи для заданного
pidвregistryв произвольном порядке.- lookup(registry, key)
Находит пару
{pid, value}для заданногоkeyвregistryв произвольном порядке.- match(registry, key, pattern, guards \\ [])
Возвращает пары
{pid, value}под заданнымkeyвregistry, которые соответствуютpattern.- meta(registry, key)
Считывает метаданные реестра, заданные в
start_link/1.- put_meta(registry, key, value)
Сохраняет метаданные реестра.
- register(registry, key, value)
Регистрирует текущий процесс под заданным
keyвregistry.- select(registry, spec)
Выбирает ключ, pid и значения, зарегистрированные с использованием полных спецификаций соответствия.
- start_link(options)
Запускает реестр как процесс-надзорщик.
- unregister(registry, key)
Дерегистрирует все записи для заданного
key, связанных с текущим процессом вregistry.- unregister_match(registry, key, pattern, guards \\ [])
Дерегистрирует записи для ключей, соответствующих шаблону, связанных с текущим процессом в
registry.- update_value(registry, key, callback)
Обновляет значение для
keyдля текущего процесса в уникальномregistry.- values(registry, key, pid)
Считывает значения для заданного
keyдляpidвregistry.
Типы
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
Тип реестра
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 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}}.
Если реестр имеет повторяющиеся ключи, несколько регистраций текущего процесса под одним и тем же ключом разрешены.
Примеры
Регистрация в уникальном реестре не допускает нескольких записей:
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— список именованных процессов, которые уведомляются о событиях:registerи:unregister. Зарегистрированный процесс должен отслеживаться слушателем, если слушатель хочет быть уведомлен о сбое зарегистрированного процесса. -
:meta— список ключевых слов метаданных, которые должны быть присоединены к реестру.
unregister(registry, key)Source
@spec unregister(registry(), key()) :: :ok
Удаляет все записи для данного key, связанного с текущим процессом в registry.
Всегда возвращает :ok и автоматически разрывает связь текущего процесса с владельцем, если больше нет ключей, связанных с текущим процессом. См. также register/3 для получения дополнительной информации о «владельце».
Примеры
Для уникальных реестров:
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.13.4/Registry.html