11.1 spawn(生み出す) - spawn
spawn でプロセスを生み出せる。
実行するとPIDがかえってくる。
iex> spawn fn -> 1 + 2 end
#PID<0.68.0>
プロセスが生きてるかどうかは Proscess.alive?/1 で確認できる。
生み出したプロセスはすぐ死ぬ。
iex> pid = spawn fn -> 1 + 2 end
#PID<0.64.0>
iex> Process.alive?(pid)
false
self/0 で今現在生きているプロセスの PID を取得できる
iex> self
#PID<0.57.0>
iex> Process.alive? self
true # 生きてる生きてる
11.2 送信と受信 - send and receive
自身のプロセスに処理を投げてみる。
iex> send self, {:hello, "world"}
{:hello, "world"}
iex> receive do
...> {:hello, msg} -> msg
...> {:world, msg} -> "won't match"
...> end
"world"
# send self, {:hello, "world"} がメールボックスに入っていたので処理してる
iex> send self, {:world, "world"}
{:world, "world"}
iex> receive do
...> {:world, msg} -> "won't match"
...> end
"won't match"
# send self, {:world, "world"} がメールボックスに入っていたので処理してる
iex> send self, {:world, "world"}
{:world, "world"}
iex> send self, {:hello, "world"}
{:hello, "world"}
iex> receive do
...> {:hello, msg} -> msg
...> {:world, msg} -> "won't match"
...> end
"won't match"
# 最初に突っ込まれてるキューが実行されて {:world, msg} がパターンマッチして "won't match" が出力されている
iex> receive do
...> {:hello, msg} -> msg
...> {:world, msg} -> "won't match"
...> end
"world"
# 二番目に突っ込まれてるキューが実行される
iex> receive do
...> {:hello, msg} -> msg
...> {:world, msg} -> "won't match"
...> end
# メッセージ待ち
after でタイムアウトも指定できる
以下は 1秒待ってタイムアウトする場合
receive do
{:hello, msg} -> msg
after
1_000 -> "nothing after 1s"
end
プロセス立ち上げて自身のプロセスに投げて処理させる。
iex> parent = self()
#PID<0.57.0>
iex> spawn fn -> send(parent, {:hello, self()}) end
#PID<0.85.0>
iex> spawn fn -> send(parent, {:hello, self()}) end
#PID<0.87.0>
iex> receive do
...> {:hello, pid} -> "Got hello from #{inspect pid}"
...> end
"Got hello from #PID<0.85.0>"
iex> receive do
...> {:hello, pid} -> "Got hello from #{inspect pid}"
...> end
"Got hello from #PID<0.87.0>"
flush でスタックされてるプロセスを flush できる。
iex> spawn fn -> send(parent, {:hello, self()}) end
#PID<0.89.0>
iex> spawn fn -> send(parent, {:hello, self()}) end
#PID<0.91.0>
iex> spawn fn -> send(parent, {:hello, self()}) end
#PID<0.93.0>
iex(31)> flush
{:hello, #PID<0.89.0>}
{:hello, #PID<0.91.0>}
{:hello, #PID<0.93.0>}
:ok
iex> flush
:ok
11.3 リンク - Links
普通に spawn して失敗した時は、プロセス同士は独立しているため、プロセス内でエラーを吐くだけで、親プロセスには失敗が伝搬されない。
iex> spawn fn -> raise "oops" end
#PID<0.61.0>
iex(4)>
01:09:44.474 [error] Process #PID<0.61.0> raised an exception
** (RuntimeError) oops
:erlang.apply/2
nil
iex>
伝搬させるには spawn_link を使用する。
iex(6)> spawn_link fn -> raise "oops" end
** (EXIT from #PID<0.65.0>) an exception was raised:
** (RuntimeError) oops
:erlang.apply/2
01:13:07.491 [error] Process #PID<0.72.0> raised an exception
** (RuntimeError) oops
:erlang.apply/2
Interactive Elixir (1.2.2) - press Ctrl+C to exit (type h() ENTER for help)
iex(1)>
- プロセスが死んでしまった際に検知し,新しいプロセスを動かすためにリンクを使用する。
- デフォではプロセスとスーパーバイザー(監視役)間では何も共有しないため
- プロセスが独立しているからプロセス内での失敗が他のプロセスをクラッシュさせたり状態を壊したりすることはない
他の言語では例外は受けとったり操作することが求められますが,Elixirではスーパーバイザーがシステムを再起動してくれるであろうため,プロセスは失敗するがままでかまいません."早く失敗する"はElixirでソフトウェアを書く際の一般的な指針です!
プロセスを殺してナンボの世界。
11.4 状態 - State
状態を持つときはプロセス上に持つ。
状態を持つプロセスを無限ループで立ち上げておく。
以下キーバリューストアの例
kv.exs
defmodule KV do
# プロセス立ち上げ
def start_link do
{:ok, spawn_link(fn -> loop(%{}) end)}
end
# 最終的に loop/1 を呼び出してループしている
defp loop(map) do
receive do
{:get, key, caller} ->
send caller, Map.get(map, key)
loop(map)
{:put, key, value} ->
loop(Map.put(map, key, value))
end
end
end
使ってみる
kv.exs
iex(6)> {:ok, pid} = KV.start_link
{:ok, #PID<0.69.0>}
iex(7)> send pid, {:get, :hello, self()}
{:get, :hello, #PID<0.61.0>}
iex(8)> flush
nil # 存在していないので nil がかえる
:ok
iex(9)> send pid, {:put, :hello, :world}
{:put, :hello, :world}
iex(10)> send pid, {:get, :hello, self()}
{:get, :hello, #PID<0.61.0>}
PIDに名前つけてエイリアス的なこともできる
iex> Process.register(pid, :kv)
true
iex> send :kv, {:get, :hello, self()}
{:get, :hello, #PID<0.61.0>}
iex> flush
:world
だがしかし
上でやってきたことはすでに Elixir が提供している。
iex> {:ok, pid} = Agent.start_link(fn -> %{} end)
{:ok, #PID<0.90.0>}
iex> Agent.update(pid, fn map -> Map.put(map, :hello, :world) end)
:ok
iex> Agent.get(pid, fn map -> Map.get(map, :hello) end)
:world
# 書き換える
iex> Agent.update(pid, fn map -> Map.put(map, :hello, :hogehoge) end)
:ok
iex> Agent.get(pid, fn map -> Map.get(map, :hello) end)
:hogehoge