Spec-Zone.ru › Elixir 1.9

Агент

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

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

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

Агенты обеспечивают разделение между клиентским и серверным 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) вне агента означает, что состояние агента может быть обновлено тем временем. Это особенно важно в случае обновлений, так как вычисление нового состояния на клиенте, а не на сервере, может привести к гонкам, если несколько клиентов пытаются обновить одно и то же состояние до разных значений.

Как контролировать

Агент чаще всего запускается в дереве контроля. Когда мы вызываем 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.

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

Агент подчиняется тем же правилам регистрации имен, что и GenServer. Узнайте больше об этом в документации по 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()

Specs

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

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

name()

Specs

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

Имя агента

on_start()

Specs

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

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

state()

Specs

state() :: term()

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

END_OF_DOCUMENT_MARKER

Функции

cast(agent, fun)

Характеристики

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

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

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

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

cast(agent, module, fun, args)

Характеристики

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

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

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

child_spec(arg)

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

См. раздел «Спецификация подчинённого агента» в модуле Supervisor для более подробной информации.

get(agent, fun, timeout \\ 5000)

Характеристики

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)

Характеристики

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

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

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

get_and_update(agent, fun, timeout \\ 5000)

Характеристики

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)

Характеристики

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

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

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

start(fun, options \\ [])

Характеристики

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 \\ [])

Характеристики

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

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

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

start_link(fun, options \\ [])

Характеристики

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 \\ [])

Характеристики

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

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

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

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

Характеристики

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)

Характеристики

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)

Характеристики

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

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

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

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

Spec-Zone.ru

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