はじめてなElixir(12)いろんな方法で並行プロセスを作る(失敗編) が、やったこととうまくいっていないところも書いてくれているので手助けしやすい気がする。応援の気持ちをこめ、やりたいことができるように書いてみます。
プロセスの終了を捕まえる
spawn_link でプロセスの終了を捕まえるで、何の反応もなくプロセスが終了しているように見えてしまいます。実はメッセージにプロセス終了のお知らせがきているのでした。
Process.flag(:trap_exit, true)
を宣言したプロセスは、そのプロセスから spawn_link した先のプロセスが異常終了したときに メッセージ を受けとります。ですから receive do mes
の mes
を表示してみると内容が表示されているはずです。
メッセージの中身を表示するようにした差分です
$ diff -w link10.orig.ex link10.ex
17c17
< receive do mes -> mes
---
> receive do mes -> IO.inspect({:test_next_temp, mes})
25c25
< receive do mes -> mes end
---
> receive do mes -> IO.inspect({:test_sensor, mes}) end
コードです
defmodule Link10 do
def pseudo_temp_sensor(temp) do
receive do
{sender, {:init, init_temp}} ->
send(sender, init_temp)
pseudo_temp_sensor(init_temp)
{sender, :next} ->
if(Enum.random(0..9) == 0, do: exit(:boom))
send(sender, temp)
cur = temp + Enum.random(0..3) * Enum.random(-1..1)
pseudo_temp_sensor(cur)
end
end
def test_next_temp(pid) do
send(pid, {self(), :next})
receive do mes -> IO.inspect({:test_next_temp, mes})
end
end
def test_sensor(zero_th, num) do
Process.flag(:trap_exit, true) # これを追加
pid = spawn_link(Link10, :pseudo_temp_sensor, [nil])
send(pid, {self(), {:init, zero_th}})
receive do mes -> IO.inspect({:test_sensor, mes}) end
1..num |> Enum.map(fn(_void) -> test_next_temp(pid) end)
end
end
これを動かしてみると
$ iex
Erlang/OTP 21 [erts-10.1] [source] [64-bit] [smp:8:8] [ds:8:8:10] [async-threads:1] [hipe] [dtrace]
Interactive Elixir (1.7.3) - press Ctrl+C to exit (type h() ENTER for help)
iex(1)> c "link10.ex"
[Link10]
iex(2)> Link10.test_sensor(0,10)
{:test_sensor, 0}
{:test_next_temp, 0}
{:test_next_temp, 0}
{:test_next_temp, -3}
{:test_next_temp, -1}
{:test_next_temp, -1}
{:test_next_temp, -1}
{:test_next_temp, -1}
{:test_next_temp, -3}
{:test_next_temp, {:EXIT, #PID<0.113.0>, :boom}}
{:EXIT, #PID<0.113.0>, :boom}
というメッセージが取得できていることがわかりますね。
spawn_monitor
さらに別のプロセスの立て方を試す (monitor) でうまくいかないのは、 spawn
と spawn_monitor
で返り値が異なるためです。
spawn は pid
が返ります。
spawn_monitor は二要素のタプル({x, y}
で表わされている)が返ります。ですから pid を使いたいなら {pid, ref} = spawn_monitor...
で受けとるといいです。
$ iex
Erlang/OTP 21 [erts-10.1] [source] [64-bit] [smp:8:8] [ds:8:8:10] [async-threads:1] [hipe] [dtrace]
Interactive Elixir (1.7.3) - press Ctrl+C to exit (type h() ENTER for help)
iex(1)> spawn fn -> nil end
#PID<0.107.0>
iex(2)> spawn_monitor fn -> nil end
{#PID<0.109.0>, #Reference<0.2955252636.2417754114.137078>}
iex(3)> {pid, ref} = spawn_monitor fn -> nil end
{#PID<0.111.0>, #Reference<0.2955252636.2417754114.137093>}
iex(4)> pid
#PID<0.111.0>
iex(5)> ref
#Reference<0.2955252636.2417754114.137093>
元記事のコードだと
send(pid, {self(), {:init, zero_th}})
が
send({#PID<0.111.0>, #Reference<0.2955252636.2417754114.137093>}, {self(), {:init, zero_th}})
という形での呼び出しに相当してしまうのでうまくいかなかったのですね。
こんな感じです。応援してます。