LoginSignup
6
1

More than 5 years have passed since last update.

はじめてなElixir(12)でうまくいっていなさそうなところを説明する

Posted at

はじめてなElixir(12)いろんな方法で並行プロセスを作る(失敗編) が、やったこととうまくいっていないところも書いてくれているので手助けしやすい気がする。応援の気持ちをこめ、やりたいことができるように書いてみます。

プロセスの終了を捕まえる

spawn_link でプロセスの終了を捕まえるで、何の反応もなくプロセスが終了しているように見えてしまいます。実はメッセージにプロセス終了のお知らせがきているのでした。

Process.flag(:trap_exit, true) を宣言したプロセスは、そのプロセスから spawn_link した先のプロセスが異常終了したときに メッセージ を受けとります。ですから receive do mesmes を表示してみると内容が表示されているはずです。

メッセージの中身を表示するようにした差分です

$ 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) でうまくいかないのは、 spawnspawn_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}}) という形での呼び出しに相当してしまうのでうまくいかなかったのですね。


こんな感じです。応援してます。

6
1
1

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
6
1