Регистр
Локальное, децентрализованное и масштабируемое хранилище процессов ключ-значение.
Он позволяет разработчикам искать один или несколько процессов по заданному ключу. Если регистр имеет :unique ключей, ключ указывает на 0 или 1 процесс. Если регистр допускает :duplicate ключей, один ключ может указывать на любое количество процессов. В обоих случаях разные ключи могут идентифицировать один и тот же процесс.
Каждая запись в регистре связана с процессом, который зарегистрировал ключ. Если процесс завершается, ключи, связанные с этим процессом, автоматически удаляются. Все сравнения ключей в регистре выполняются с помощью операции совпадения (===/2).
Регистр можно использовать для различных целей, таких как поиск по имени (используя :via опцию), хранение свойств, настройка правил диспетчеризации или реализации системы pub/sub. Мы рассмотрим некоторые из этих вариантов использования ниже.
Регистр также может быть прозрачно разбит на разделы, что обеспечивает более масштабируемое поведение для работы регистров в высококонкурентных средах с тысячами или миллионами записей.
Использование в :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. Обычно регистр запускается в рамках дерева контроля (supervision tree):
{Registry, keys: :unique, name: Registry.ViaTest}
Только регистры с уникальными ключами могут быть использованы в :via. Если имя уже занято, специфичная для данного случая функция start_link (Agent.start_link/2 в примере выше) вернёт {:error, {:already_started, current_pid}}.
Использование в качестве диспетчера
Registry имеет механизм диспетчеризации, который позволяет разработчикам реализовывать пользовательскую логику диспетчеризации, запускаемую отзывающимся (caller). Например, предположим, что у нас есть дублируемый регистр, запущенный следующим образом:
{: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, явно отправляя сообщения. Это пример, который мы рассмотрим далее.
Использование в качестве Pub/Sub
Регистры также могут быть использованы для реализации локальной, нераспределённой, масштабируемой системы Pub/Sub, опираясь на функцию 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()
Тип реестра.
- match_pattern()
Шаблон для сопоставления с объектами в реестре.
- meta_key()
Тип ключей метаданных реестра.
- meta_value()
Тип значений метаданных реестра.
- registry()
Идентификатор реестра.
- spec()
Полная спецификация совпадения, используемая при выборе объектов в реестре.
- start_option()
Параметры, используемые для
child_spec/1иstart_link/1.- value()
Тип значений, разрешённых в реестре.
Функции
- child_spec(options)
Возвращает спецификацию для запуска реестра под надзором.
- count(registry)
Возвращает количество зарегистрированных ключей в реестре. Выполняется за постоянное время.
- count_match(registry, key, pattern, guards \\ [])
Возвращает количество
{pid, value}пар под заданнымkeyвregistry, которые соответствуютpattern.- count_select(registry, spec)
Работает как
select/2, но возвращает только количество совпадающих записей.- delete_meta(registry, key)
Удаляет метаданные реестра для заданного
keyвregistry.- dispatch(registry, key, mfa_or_fun, opts \\ [])
Вызывает обратный вызов со всеми записями под
keyв каждом разделе для заданногоregistry.- keys(registry, pid)
Возвращает известные ключи для заданного
pidвregistryв произвольном порядке.- lookup(registry, key)
Находит
{pid, value}пару для заданногоkeyвregistryв произвольном порядке.- match(registry, key, pattern, guards \\ [])
Возвращает
{pid, value}пары под заданнымkeyвregistry, которые соответствуютpattern.- meta(registry, key)
Считывает метаданные реестра, заданные в
start_link/1.- put_meta(registry, key, value)
Сохраняет метаданные реестра.
- register(registry, key, value)
Регистрирует текущий процесс под заданным
keyвregistry.- select(registry, spec)
Выбирает ключ, pid и значения, зарегистрированные с использованием полных спецификаций соответствия.
- start_link(options)
Запускает реестр как процесс-надзор.
- unregister(registry, key)
Удаляет все записи для заданного
key, связанного с текущим процессом вregistry.- unregister_match(registry, key, pattern, guards \\ [])
Удаляет записи для ключей, соответствующих шаблону, связанному с текущим процессом в
registry.- update_value(registry, key, callback)
Обновляет значение для
keyдля текущего процесса в уникальномregistry.- values(registry, key, pid)
Считывает значения для заданного
keyдляpidвregistry.
Типы
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 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}}.
Если реестр имеет дублирующиеся ключи, несколько регистраций текущего процесса под одним и тем же ключом разрешены.
Примеры
Регистрация в уникальном реестре не позволяет нескольким записям:
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.14.1/Registry.html