Erlang 基礎ポイント6 - 並行処理3

今日はErlangの並行処理で、モニタについて勉強します。

モニタとは

あるErlangプロセスを監視したいが、リンクを作ると、お互いのErlangプロセスが
密接に関連するようになるので、気をつけなければなりません。
監視のことでプログアムに障害が起きたり、構造が複雑になっては本末転倒ですね。

こういう時、モニタを使います。
監視したいErlangプロセスと直接付き合わなくても(リンクしなくても)、
監視できるようにするものがモニタなのです。

次のような関数を呼び出して現在のErlangプロセスをモニタにできます。

monitor(process, 監視対象のErlangプロセスのPID)

リンクは双方向

リンクの場合、どちらかのErlangプロセスが死んだら、死んでいない方のErlangプロセスに
必ず、そのシグナルが送られます。
つまり、双方向の特性を持っています。
以下のサンプルソースを見て下さい。

% monitor_test1.erl
-module(monitor_test1).
-export([monitor_test1/0]).

monitor_test1() ->
  PPid = spawn(fun p/0),
  register(parent, PPid).

p() ->
  CPid = spawn_link(fun c/0), % Erlangプロセス間でリンクを作成
  register(child, CPid),
  loop("Parent").

c() ->
  loop("Child").

loop(Msg) ->
  receive
    exit ->
      exit;
    error ->
      1 / 0;
    Any ->
      io:format("Any ~p ~n", [Any]),
      loop(Msg)
  after 2000 ->
    io:format("Process ~p ~n", [Msg]),
    loop(Msg)
  end.

% REPL環境で試したものです。
1> c(monitor_test1).
./monitor_test1.erl:22: Warning: this expression will fail with a 'badarith' exception 
{ok,monitor_test1}
2> monitor_test1:monitor_test1().
true
Process "Child" 
Process "Parent" 
Process "Parent" 
Process "Child" 
3> parent ! error. % 親Erlangプロセスを止めたら、子も止まりました。

=ERROR REPORT==== 24-Jun-2014::12:39:32 ===
Error in process <0.39.0> with exit value: {badarith,[{monitor_test1,loop,1}]}

error
4> monitor_test1:monitor_test1().
true
Process "Child" 
Process "Parent" 
Process "Parent" 
Process "Child"  
5> child ! error.% 子Erlangプロセスを止めたら、親も止まりました。
=ERROR REPORT==== 24-Jun-2014::12:39:45 ===
Error in process <0.44.0> with exit value: {badarith,[{monitor_test1,loop,1}]}

error

親と子のどちらかが終了すると、必ず片方も終了するのが分かります。
spawn_linkでErlangプロセス間でリンクを作ったから、当然な結果ですね。

しかし、監視だけのためにリンクを作ると、親と子の間を制御するのに面倒そうですね。
モニタの方を見てみましょう。

モニタは一方向

それに比べると、モニタの場合は一方向です。
モニタになったErlangプロセスは監視対象のErlangプロセスが死んだら、そのシグナルを補足できますが、
逆にモニタのErlangプロセスが死んでも監視対象のErlangプロセスには影響が及びません。

サンプルソースを見てみましょう。
monitor_test2関数が二つあることに注意して下さい。

% monitor_test2.erl
-module(monitor_test2).
-export([monitor_test2/0, monitor_test2/1]).

monitor_test2() -> monitor_test2(fun(CPid) -> CPid end).

monitor_test2(F) ->
  PPid = spawn(fun() -> p(F) end),
  register(parent, PPid).

p(F) ->
  CPid = spawn(fun c/0), % 親と子の間は何の関連もない。
  register(child, CPid),
  F(CPid),
  loop("Parent").

c() ->
  loop("Child").

loop(Msg) ->
  receive
    exit ->
      exit;
    error ->
      1 / 0;
    Any ->
      io:format("Any ~p ~n", [Any]),
      loop(Msg)
  after 2000 ->
    io:format("Process ~p ~n", [Msg]),
    loop(Msg)
  end.

% REPL環境で試したものです。
1> c(monitor_test2).
./monitor_test2.erl:25: Warning: this expression will fail with a 'badarith' exception 
{ok,monitor_test2}
2> monitor_test2:monitor_test2().
true
Process "Child" 
Process "Parent" 
Process "Parent" 
Process "Child" 
3> child ! error. % 子Erlangプロセスを止めても、

=ERROR REPORT==== 24-Jun-2014::13:19:23 ===
Error in process <0.40.0> with exit value: {badarith,[{monitor_test2,loop,1}]}

error
Process "Parent"  % 親Erlangプロセスは生きています。
Process "Parent" 
Process "Parent"  
4> parent ! error. % 親Erlangプロセスを止めたら、やっと、全部止まりました。

=ERROR REPORT==== 24-Jun-2014::13:19:28 ===
Error in process <0.39.0> with exit value: {badarith,[{monitor_test2,loop,1}]}

error

Erlangプロセス生成時、spawn_linkでなく、spawnを使ったから、
リンクは作られず、終了シグナルの伝播は起こりません。
しかし、以下のように親Erlangプロセスをモニタにすると、

5> monitor_test2:monitor_test2(fun(CPid) -> monitor(process, CPid) end). % 子Erlangプロセス(CPid)を監視
true
Process "Child" 
Process "Parent" 
Process "Parent" 
Process "Child" 
6> child ! error.  % 子Erlangプロセスを止めたら、

=ERROR REPORT==== 24-Jun-2014::13:21:13 ===
Error in process <0.45.0> with exit value: {badarith,[{monitor_test2,loop,1}]}

% 親Erlangプロセスでそれを補足できました!
Any {'DOWN',#Ref<0.0.0.86>,process,<0.45.0>,   
            {badarith,[{monitor_test2,loop,1}]}} 
error
Process "Parent"  % 親プロセスは動きつづけています。
Process "Parent" 
7> parent ! exit.
exit

上記のように監視対象(子Erlangプロセス)を監視できて、モニタとなるErlangプロセスは
生きていることが分かります。

逆に以下のようにモニタとなるErlangプロセスが終了しても、

8> monitor_test2:monitor_test2(fun(CPid) -> monitor(process, CPid) end).
true
Process "Child" 
Process "Parent" 
Process "Parent" 
Process "Child" 
Process "Child" 
Process "Parent" 
Process "Parent" 
Process "Child"  
9> parent ! error. % モニタのErlangプロセスを止めても、

=ERROR REPORT==== 24-Jun-2014::13:23:30 ===
Error in process <0.49.0> with exit value: {badarith,[{monitor_test2,loop,1}]}

error
Process "Child" % 監視対象のErlangプロセスは動きつづける。

監視対象のErlangプロセスには何の影響もありません。
これでモニタはリンクと違って、終了シグナルが一方向にしか送られないことを
確認できました。

システムプロセスとの違い

モニタを使わずにspawn_linkでリンクを作ってシステムプロセスにしてもいいんじゃない?と
疑問を抱くかもしれません。
が、リンクの場合、双方向に終了シグナルが送られるので、監視される側も、監視する側の終了シグナルに
備えるため、システムプロセスにする必要があります。
それに加えてお互いのメッセージを監視しなければなりません。
つまりいうと、監視処理を入れるため、システムプロセスを使うと、プログラムが複雑になるということです。
なので、監視のためなら、その言葉どおり(監視 = monitor)、モニタを使えばいいと思います。

参考

プログラミングErlang

プログラミングErlang

http://www.ymotongpoo.com/works/lyse-ja/ja/14_errors_and_processes.html#id4
http://erlangworld.web.fc2.com/concurrent_programming/monitor.html

今日はここまです。
次回は分散システムについて勉強したいと思います。