Spec-Zone.ru › Elixir 1.17

Источник Agent

Agents — это простое абстрагирование состояния.

Часто в Elixir требуется обмениваться или хранить состояние, к которому необходимо получить доступ из разных процессов или из одного процесса в разное время.

Модуль Agent предоставляет базовую реализацию сервера, которая позволяет получать и обновлять состояние с помощью простого API.

Примеры

Например, следующий агент реализует счётчик:

defmodule Counter do
  use Agent

  def start_link(initial_value) do
    Agent.start_link(fn -> initial_value end, name: __MODULE__)
  end

  def value do
    Agent.get(__MODULE__, & &1)
  end

  def increment do
    Agent.update(__MODULE__, &(&1 + 1))
  end
end

Использование будет следующим:

Counter.start_link(0)
#=> {:ok, #PID<0.123.0>}

Counter.value()
#=> 0

Counter.increment()
#=> :ok

Counter.increment()
#=> :ok

Counter.value()
#=> 2

Благодаря процессу-серверу агента, счётчик можно безопасно увеличивать одновременно.

use Agent

Когда вы use Agent, модуль Agent определит функцию child_spec/1, поэтому ваш модуль может быть использован как дочерний в дереве надзора.

Агенты обеспечивают разделение между клиентским и серверным API (подобно GenServer). В частности, функции, передаваемые в качестве аргументов вызовам функций Agent, вызываются внутри агента (сервера). Это различие важно, потому что вы можете захотеть избежать дорогостоящих операций внутри агента, так как они фактически заблокируют агент до тех пор, пока запрос не будет выполнен.

Рассмотрим эти два примера:

# Compute in the agent/server
def get_something(agent) do
  Agent.get(agent, fn state -> do_something_expensive(state) end)
end

# Compute in the agent/client
def get_something(agent) do
  Agent.get(agent, & &1) |> do_something_expensive()
end

Первая функция блокирует агент. Вторая функция копирует всё состояние к клиенту, а затем выполняет операцию на клиенте. Один аспект, который нужно учитывать, — достаточно ли велики данные, чтобы потребовать обработки на сервере, по крайней мере, изначально, или они достаточно малы, чтобы их можно было недорого отправить клиенту. Другой фактор — требуется ли обработка данных атомарно: получение состояния и вызов do_something_expensive(state) вне агента означает, что состояние агента может быть обновлено тем временем. Это особенно важно в случае обновлений, так как вычисление нового состояния на клиенте, а не на сервере, может привести к гонкам, если несколько клиентов пытаются обновить одно и то же состояние до разных значений.

Как настроить надзор

Агент Agent чаще всего запускается в дереве надзора. Когда мы вызываем use Agent, он автоматически определяет функцию child_spec/1, которая позволяет нам запустить агент непосредственно под надзором. Чтобы запустить агент под надзором с начальным счётчиком 0, можно сделать так:

children = [
  {Counter, 0}
]

Supervisor.start_link(children, strategy: :one_for_all)

Хотя можно также просто передать Counter в качестве дочернего элемента в надзор, например:

children = [
  Counter # Same as {Counter, []}
]

Supervisor.start_link(children, strategy: :one_for_all)

Это определение не сработает для этого конкретного примера, так как оно попытается запустить счётчик с начальным значением пустого списка. Однако это может быть жизнеспособным вариантом в ваших собственных агентах. Общим подходом является использование списка ключевых слов, так как это позволит установить начальное значение и дать имя процессу счётчика, например:

def start_link(opts) do
  {initial_value, opts} = Keyword.pop(opts, :initial_value, 0)
  Agent.start_link(fn -> initial_value end, opts)
end

и затем вы можете использовать Counter, {Counter, name: :my_counter} или даже {Counter, initial_value: 0, name: :my_counter} в качестве спецификации дочернего элемента.

use Agent также принимает список параметров, которые настраивают спецификацию дочернего элемента и, следовательно, как он работает под надзором. Сгенерированная child_spec/1 может быть настраиваема с помощью следующих параметров:

  • :id — идентификатор спецификации дочернего элемента, по умолчанию — текущий модуль
  • :restart — когда дочерний элемент должен быть перезапущен, по умолчанию — :permanent
  • :shutdown — как остановить дочерний элемент, либо немедленно, либо дав ему время на остановку

Например:

use Agent, restart: :transient, shutdown: 10_000

См. раздел «Спецификация дочернего элемента» в модуле Supervisor для получения более подробной информации. Аннотация @doc непосредственно перед use Agent будет добавлена к сгенерированной функции child_spec/1.

Регистрация имен

Агент подчиняется тем же правилам регистрации имен, что и GenServers. Подробнее об этом читайте в документации к GenServer.

Несколько слов о распределённых агентах

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

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

Обратите внимание, что эта проблема также проявляется при выполнении «постепенных обновлений» с агентами. Под постепенными обновлениями мы понимаем следующую ситуацию: вы хотите развернуть новую версию своего программного обеспечения, выключив некоторые из ваших узлов и заменив их узлами, работающими с новой версией программного обеспечения. В этой настройке часть вашей среды будет иметь одну версию данного модуля, а другая часть — другую версию (новую) того же модуля.

Лучшим решением является просто использование API с явным модулем, функцией и аргументами при работе с распределёнными агентами.

Горячая замена кода

Код агента можно заменить «на лету», просто передав кортеж из модуля, функции и аргументов в инструкцию обновления. Например, предположим, что у вас есть агент с именем :sample и вы хотите преобразовать его внутреннее состояние со списка ключевых слов в карту. Это можно сделать с помощью следующей инструкции:

{:update, :sample, {:advanced, {Enum, :into, [%{}]}}}

Состояние агента будет добавлено к указанному списку аргументов ([%{}]) в качестве первого аргумента.

Резюме

Типы

agent()

Ссылка на агент

name()

Имя агента

on_start()

Возвращаемые значения функций start*

state()

Состояние агента

Функции

cast(agent, fun)

Выполняет операцию броска (fire and forget) состояния агента.

cast(agent, module, fun, args)

Выполняет операцию броска (fire and forget) состояния агента.

child_spec(arg)

Возвращает спецификацию для запуска агента под надзором.

get(agent, fun, timeout \\ 5000)

Получает значение агента с помощью данной анонимной функции.

get(agent, module, fun, args, timeout \\ 5000)

Получает значение агента с помощью данной функции.

get_and_update(agent, fun, timeout \\ 5000)

Получает и обновляет состояние агента в одной операции с помощью данной анонимной функции.

get_and_update(agent, module, fun, args, timeout \\ 5000)

Получает и обновляет состояние агента в одной операции с помощью данной функции.

start(fun, options \\ [])

Запускает процесс агента без ссылок (вне дерева надзора).

start(module, fun, args, options \\ [])

Запускает агент без ссылок с заданным модулем, функцией и аргументами.

start_link(fun, options \\ [])

Запускает агент, связанный с текущим процессом, с заданной функцией.

start_link(module, fun, args, options \\ [])

Запускает агент, связанный с текущим процессом.

stop(agent, reason \\ :normal, timeout \\ :infinity)

Синхронно останавливает агент с указанным reason.

update(agent, fun, timeout \\ 5000)

Обновляет состояние агента с помощью данной анонимной функции.

update(agent, module, fun, args, timeout \\ 5000)

Обновляет состояние агента с помощью данной функции.

Типы

agent()Source

@type agent() :: pid() | {atom(), node()} | name()

Ссылка на агента

name()Source

@type name() :: atom() | {:global, term()} | {:via, module(), term()}

Имя агента

on_start()Source

@type on_start() :: {:ok, pid()} | {:error, {:already_started, pid()} | term()}

Возвращаемые значения функций start*

state()Source

@type state() :: term()

Состояние агента

Функции

cast(agent, fun)Source

@spec cast(agent(), (state() -> state())) :: :ok

Выполняет операцию cast (fire and forget) над состоянием агента.

Функция fun отправляется agent, который вызывает функцию, передавая состояние агента. Возвращаемое значение fun становится новым состоянием агента.

Обратите внимание, что cast возвращает :ok немедленно, независимо от того, существует ли agent (или узел, на котором он должен находиться).

Примеры

iex> {:ok, pid} = Agent.start_link(fn -> 42 end)
iex> Agent.cast(pid, fn state -> state + 1 end)
:ok
iex> Agent.get(pid, fn state -> state end)
43

cast(agent, module, fun, args)Source

@spec cast(agent(), module(), atom(), [term()]) :: :ok

Выполняет операцию cast (fire and forget) над состоянием агента.

Аналогично cast/2, но вместо анонимной функции ожидаются модуль, функция и аргументы. Состояние добавляется в качестве первого аргумента к заданному списку аргументов.

Примеры

iex> {:ok, pid} = Agent.start_link(fn -> 42 end)
iex> Agent.cast(pid, Kernel, :+, [12])
:ok
iex> Agent.get(pid, fn state -> state end)
54

child_spec(arg)Source

Возвращает спецификацию для запуска агента под управлением надсмотрщика.

См. раздел "Спецификация ребенка" в модуле Supervisor для получения более подробной информации.

get(agent, fun, timeout \\ 5000)Source

@spec get(agent(), (state() -> a), timeout()) :: a when a: var

Получает значение агента через заданную анонимную функцию.

Функция fun отправляется agent, который вызывает функцию, передавая состояние агента. Результат вызова функции возвращается из этой функции.

timeout — целое число, большее нуля, которое указывает, сколько миллисекунд разрешено, прежде чем агент выполнит функцию и вернет результат, или атом :infinity, чтобы ждать неопределённо долго. Если результат не получен в течение указанного времени, вызов функции завершается ошибкой, и вызывающий процесс завершается.

Примеры

iex> {:ok, pid} = Agent.start_link(fn -> 42 end)
iex> Agent.get(pid, fn state -> state end)
42

get(agent, module, fun, args, timeout \\ 5000)Source

@spec get(agent(), module(), atom(), [term()], timeout()) :: any()

Получает значение агента через заданную функцию.

Аналогично get/3, но вместо анонимной функции ожидаются модуль, функция и аргументы. Состояние добавляется в качестве первого аргумента к заданному списку аргументов.

get_and_update(agent, fun, timeout \\ 5000)Source

@spec get_and_update(agent(), (state() -> {a, state()}), timeout()) :: a when a: var

Получает и обновляет состояние агента в одной операции через заданную анонимную функцию.

Функция fun отправляется agent, который вызывает функцию, передавая состояние агента. Функция должна возвращать кортеж с двумя элементами: первым — значение для возврата (значение «получить»), а вторым — новое состояние агента.

timeout — целое число, большее нуля, которое указывает, сколько миллисекунд разрешено, прежде чем агент выполнит функцию и вернет результат, или атом :infinity, чтобы ждать неопределённо долго. Если результат не получен в течение указанного времени, вызов функции завершается ошибкой, и вызывающий процесс завершается.

Примеры

iex> {:ok, pid} = Agent.start_link(fn -> 42 end)
iex> Agent.get_and_update(pid, fn state -> {state, state + 1} end)
42
iex> Agent.get(pid, fn state -> state end)
43

get_and_update(agent, module, fun, args, timeout \\ 5000)Source

@spec get_and_update(agent(), module(), atom(), [term()], timeout()) :: any()

Получает и обновляет состояние агента в одной операции через заданную функцию.

Аналогично get_and_update/3, но вместо анонимной функции ожидаются модуль, функция и аргументы. Состояние добавляется в качестве первого аргумента к заданному списку аргументов.

start(fun, options \\ [])Source

@spec start((-> term()), GenServer.options()) :: on_start()

Запускает процесс агента без связей (вне дерева надзора).

См. start_link/2 для получения дополнительной информации.

Примеры

iex> {:ok, pid} = Agent.start(fn -> 42 end)
iex> Agent.get(pid, fn state -> state end)
42

start(module, fun, args, options \\ [])Source

@spec start(module(), atom(), [any()], GenServer.options()) :: on_start()

Запускает агент без связей с заданным модулем, функцией и аргументами.

См. start_link/4 для получения дополнительной информации.

start_link(fun, options \\ [])Source

@spec start_link((-> term()), GenServer.options()) :: on_start()

Запускает агент, связанный с текущим процессом, с заданной функцией.

Это часто используется для запуска агента как части дерева надзора.

После создания агента, заданная функция fun вызывается в процессе сервера и должна вернуть начальное состояние агента. Обратите внимание, что start_link/2 не возвращается, пока заданная функция не вернёт значение.

Параметры

Параметр :name используется для регистрации, как описано в документации модуля.

Если присутствует параметр :timeout, агенту разрешается тратить не более заданного количества миллисекунд на инициализацию, иначе он будет завершён, а функция запуска вернёт {:error, :timeout}.

Если параметр :debug присутствует, соответствующая функция в модуле :sys будет вызвана.

Если параметр :spawn_opt присутствует, его значение будет передано в качестве параметров подлежащему процессу, как в Process.spawn/4.

Возвращаемые значения

Если сервер успешно создан и инициализирован, функция возвращает {:ok, pid}, где pid — PID сервера. Если агент с указанным именем уже существует, функция возвращает {:error, {:already_started, pid}} с PID этого процесса.

Если заданная функция обратного вызова завершается ошибкой, функция возвращает {:error, reason}.

Примеры

iex> {:ok, pid} = Agent.start_link(fn -> 42 end)
iex> Agent.get(pid, fn state -> state end)
42

iex> {:error, {exception, _stacktrace}} = Agent.start(fn -> raise "oops" end)
iex> exception
%RuntimeError{message: "oops"}

start_link(module, fun, args, options \\ [])Source

@spec start_link(module(), atom(), [any()], GenServer.options()) :: on_start()

Запускает агент, связанный с текущим процессом.

Аналогично start_link/2, но вместо анонимной функции ожидаются модуль, функция и аргументы; fun в module будет вызван с заданными аргументами args для инициализации состояния.

stop(agent, reason \\ :normal, timeout \\ :infinity)Source

@spec stop(agent(), reason :: term(), timeout()) :: :ok

Синхронно останавливает агент с заданным reason.

Возвращает :ok если агент завершается с указанным основанием. Если агент завершается с другим основанием, вызов завершается ошибкой.

Эта функция сохраняет семантику OTP относительно сообщения об ошибках. Если причина отличается от :normal, :shutdown или {:shutdown, _}, будет записано сообщение об ошибке.

Примеры

iex> {:ok, pid} = Agent.start_link(fn -> 42 end)
iex> Agent.stop(pid)
:ok

update(agent, fun, timeout \\ 5000)Source

@spec update(agent(), (state() -> state()), timeout()) :: :ok

Обновляет состояние агента через заданную анонимную функцию.

Функция fun отправляется agent, который вызывает функцию, передавая состояние агента. Возвращаемое значение fun становится новым состоянием агента.

Эта функция всегда возвращает :ok.

timeout — целое число, большее нуля, которое указывает, сколько миллисекунд разрешено, прежде чем агент выполнит функцию и вернет результат, или атом :infinity для ожидания неопределённо долго. Если результат не получен в течение указанного времени, вызов функции завершается ошибкой, и вызывающий процесс завершается.

Примеры

iex> {:ok, pid} = Agent.start_link(fn -> 42 end)
iex> Agent.update(pid, fn state -> state + 1 end)
:ok
iex> Agent.get(pid, fn state -> state end)
43

update(agent, module, fun, args, timeout \\ 5000)Source

@spec update(agent(), module(), atom(), [term()], timeout()) :: :ok

Обновляет состояние агента с помощью заданной функции.

Аналогично update/3, но вместо анонимной функции ожидаются модуль, функция и аргументы. Состояние добавляется как первый аргумент в предоставленный список аргументов.

Примеры

iex> {:ok, pid} = Agent.start_link(fn -> 42 end)
iex> Agent.update(pid, Kernel, :+, [12])
:ok
iex> Agent.get(pid, fn state -> state end)
54

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

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

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

Spec-Zone.ru

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