Spec-Zone.ru › Elixir 1.16

Исходный код Agent

Агенты — это простое абстракция состояния.

Часто в 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)

Выполняет операцию cast (отправить и забыть) над состоянием агента.

cast(agent, module, fun, args)

Выполняет операцию cast (отправить и забыть) над состоянием агента.

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.32.2) для языка программирования Elixir

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

Spec-Zone.ru

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