読者です 読者をやめる 読者になる 読者になる

Elixir - Agent

Elixir

Agent

Agent - Elixir

Agentは状態を保持します。

# 空のリストを初期状態として生成
{:ok, agent} = Agent.start_link(fn -> [] end)

# 現在の状態(list)を引数として新しい状態を生成する
Agent.update(agent, fn list -> ["eggs" | list] end)

# 現在の状態を取得する
Agent.get(agent, fn list -> list end)

# プロセスを終了する
Agent.stop(agent)

Process.alive?(agent)
# > false

Agentを利用してキーバリューストアを実装してみます。

# kv.exs
defmodule KV do
  def start_link do
    Agent.start_link(fn -> %{} end)
  end

  def get(kv, key) do
    Agent.get(kv, &Map.get(&1, key))
  end

  def put(kv, key, value) do
    Agent.put(kv, &Map.put(&1, key, value))
  end

  def delete(kv, key) do
    Agent.get_and_update(kv, &Map.pop(&1, key))
  end
end

iexから実行してみます。

$ iex kv.exs
iex> {:ok, kv} = KV.start_link
{:ok, #PID<0.64.0>}
iex> KV.put(kv, :hello, "world")
:ok
iex> KV.get(kv, :hello)
"world"
iex> KV.delete(kv, :hello)
"world"
iex> KV.get(kv, :hello)
nil

Process間でAgentを共有してみる

一つのカウンターをマルチプロセスでカウントアップしてみます。

# counter.exs
defmodule Counter do
  def start_link do
    Agent.start_link(fn -> 0 end)
  end

  def increment(counter) do
    Agent.update(counter, fn x -> x + 1 end)
  end

  def count(counter) do
    Agent.get(counter, fn x -> x end)
  end
end

defmodule CountProcess do
  def start_link(counter, parent) do
    {:ok, spawn_link(fn -> loop(counter, parent) end)}
  end

  defp loop(counter, parent) do
    count = Counter.count(counter)

    IO.puts "#{inspect(self)} #{count}"

    cond do
      count < 10_000 ->
        Counter.increment(counter)
        loop(counter, parent)
      true ->
        IO.puts "#{inspect(self)} #{count} Completed!!"
    end
  end
end

defmodule ConcurrentCounter do
  def run(n) do
    {:ok, counter} = Counter.start_link
    Enum.each(1..n, fn x -> CountProcess.start_link(counter, self) end)
  end
end
$ iex counter.exs
iex> ConcurrentCounter.run(4)
...
#PID<0.71.0> 9999
#PID<0.70.0> 9999
#PID<0.72.0> 10003
#PID<0.69.0> 10003
#PID<0.71.0> 10003
#PID<0.70.0> 10003
#PID<0.72.0> 10003 Completed!!
#PID<0.69.0> 10003 Completed!!
#PID<0.71.0> 10003 Completed!!
#PID<0.70.0> 10003 Completed!!

複数のプロセスが起動してカウントアップしているのが確認できました。
が、同期を取っていないので10,000を超えてカウントされてしまっています。

Javaだとスレッドでsynchronizedとかして同期させた記憶があるのですが、Elixirだとsend-receive? Task? GenServer? この辺りを利用するのかな?