Edited at

Elixirチュートリアル プロセス

More than 3 years have passed since last update.


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


参考

11 プロセス - Processes