Elixir - Agent
Agent
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? この辺りを利用するのかな?