14. エラーとプロセス

14.1. リンク

リンクは2つのプロセス間で作成される特定の関係を指します。 関係が作成されて片方のプロセスが予期せぬスロー、エラーあるいは終了処理(詳しくは エラーと例外 参照)などで死んだ場合、もう片方のリンクされたプロセスも死にます。

これは可能なかぎり早くエラーを止めるために失敗するという点において、役立つ概念です。 もしエラーを持っているプロセスがクラッシュしたけれど、それに依存しているプロセスがクラッシュしなかった場合、すべての依存プロセスが依存先が無くなっていることの対応をしなければならなくなります。 それらのプロセスを殺して、関係するプロセスのグループ全体を再起動するというのが代替案として考えられます。リンクはまさにこれをやってくれるのです。

2つのプロセス間にリンクを作成するために、Erlangは Pid を引数にとる link/1 というプリミティブ関数を用意しています。 この関数が呼ばれたときには、関数は現在のプロセスと Pid で認識されるプロセスとの間にリンクを作成します。 リンクを取り除くには、 unlink/1 を使います。リンクされたプロセスの内片方が死んだ時には、特別なメッセージが、何が起きたかに関係する情報と共に送られます。 もしプロセスが自然な理由で死んだ場合には、なんのメッセージも送られません。 この新しい関数をまずは linkmon.erl の一部としてご紹介しましょう:

myproc() ->
    timer:sleep(5000),
    exit(reason).

次のような関数呼び出し(そして各spawnコマンド簡で5秒待機)をしようとした場合、リンクがこのプロセスとシェルの間に張られているときにだけ、シェルが 'reason' でクラッシュするのが分かるでしょう。

1> c(linkmon).
{ok,linkmon}
2> spawn(fun linkmon:myproc/0).
<0.52.0>
3> link(spawn(fun linkmon:myproc/0)).
true
** exception error: reason

これを図示すると:

../_images/link-exit.png

しかしながら、この {'EXIT', B, Reason} メッセージはいつものような try ... catch ではキャッチされません。それを行うには他の機構が必要です。それについてはあとで触れます。

リンクが、一緒に死ぬべき大きなプロセスグループを作るのに使われることは重要なので覚えておいてください:

chain(0) ->
    receive
        _ -> ok
    after 2000 ->
        exit("chain dies here")
    end;
chain(N) ->
    Pid = spawn(fun() -> chain(N-1) end),
    link(Pid),
    receive
        _ -> ok
    end.

この関数は整数 N をとって、Nこのプロセスがお互いにリンクされるようにします。 N-1 という引数が次の 'chain' プロセス(これが spawn/1 を呼び出す)に渡せるようにするために、無名関数の中で呼び出して、これ以上引数が必要ないようにしています。 spawn(?MODULE, chain, [N-1]) を呼び出すことで、同じようなことが出来ます。

これで、プロセスがお互いにリンクされたので、どれか一つでも死んだら終了します:

4> c(linkmon).
{ok,linkmon}
5> link(spawn(linkmon, chain, [3])).
true
** exception error: "chain dies here"

ご覧のとおり、シェルは他のプロセスから死亡シグナルを受け取っています。 ここにspawnされたプロセスのリンクがどのように落ちて行くか示します:

[shell] == [3] == [2] == [1] == [0]
[shell] == [3] == [2] == [1] == *dead*
[shell] == [3] == [2] == *dead*
[shell] == [3] == *dead*
[shell] == *dead*
*dead, error message shown*
[shell] <-- restarted

linkmon:chain(0) を走らせているプロセスが死んだ後、エラーがリンクのチェーンを伝搬して、シェルプロセスがそれが原因で死ぬまでそれが続きます。 クラッシュはリンク中のどのプロセスが死んでも起きえます。リンクは双方向なので、あるプロセスが死ねば、他のプロセスは後に続きます。

Note

シェルから他のプロセスを殺したければ、 exit/2 関数を使えます。 これは exit(Pid, Reason) のようにして呼び出せます。試してみてください。

Note

リンクはスタックできません。 link/1 を同じ2プロセスに対して15回呼び出したいときは、プロセス間にはリンクが1つだけ存在する状態になり、それを落とすのは unlink/1 を1度呼び出せば十分です。

link(spawn(Function))link(spanw(M,F,A)) は1ステップ以上で動作するということは重要なので覚えておいてください。 プロセスがリンクが作成される前に死んでしまうということもありえますし、それによって予期せぬ動作が起きることもあり得ます。 こういったことから、 spawn_link/1-3 が標準モジュールに追加されました。 spawn/1-3 としていくつかの引数をとり、プロセスとそれに対するリンクが link/1 でのものと同じように生成されます。ただしこれはアトミックに行われます。 (操作が1つに統合されて、成功するか失敗するかどちらか一つの状態になるということです) 一般的にはこちらのほうが安全で、書く括弧の数も少なくてすみます。

../_images/ackbar.jpg

14.2. それは罠だ!

リンクとプロセスの死の話に戻りましょう。 プロセスを通じてエラーの伝搬はメッセージパッシングと同様の方法で行われますが、シグナルと呼ばれる特別なメッセージで伝搬されます。 終了シグナルは「秘密の」メッセージで、プロセス上で自動的に動作して、そのプロセスを殺します。

信頼性を高めるために、アプリケーションはプロセスを殺すのと再起動するのを素早く行える必要があるということをすでに何度もお伝えしてきました。 たったいま、リンクを殺す部分に関しては大丈夫になりました。足りないのは再起動の部分です。

プロセスを再起動するために、まずプロセスが死んだかを知る必要があります。 これはリンクの上にシステムプロセスと呼ばれる概念の層(ケーキの上の美味しいフロスト)を追加することで可能になります。 システムプロセスは基本的には普通のプロセスですが、終了シグナルを普通のメッセージに変換できる部分だけ違います。 これは process_flag(trap_exit, true) を実行中のプロセス内で呼び出せばできます。 例以上のことは何も無いので、とりあえずそれを見てみましょう。 チェーンの例をもう一度やるだけですが、最初にシステムプロセスを使います:

1> process_flag(trap_exit, true).
true
2> spawn_link(fun() -> linkmon:chain(3) end).
<0.49.0>
3> receive X -> X end.
{'EXIT',<0.49.0>,"chain dies here"}

あら!面白いことになりました。絵に戻って、何が起きたか見てみましょう。こんなことが起きました:

[shell] == [3] == [2] == [1] == [0]
[shell] == [3] == [2] == [1] == *dead*
[shell] == [3] == [2] == *dead*
[shell] == [3] == *dead*
[shell] <-- {'EXIT,Pid,"chain dies here"} -- *dead*
[shell] <-- still alive!

これがプロセスを素早く再起動することを可能にする機構です。システムプロセスを使ってプログラムを書くことで、あるプロセスが死んだかどうか確認して、落ちたときにはそれを再起動するだけの役割を持ったプロセスを簡単に作ることが出来ます。 次の章でこれについてはもっと詳しく触れます。そこでどのようにこのテクニックを適用していくかも触れます。

いまは、 例外の章 に見られた例外関数に話を戻して、終了を補足するプロセス周りの動作を見てみましょう。 それに続いて近くのプロセス内のキャッチされないスロー、エラー、終了の結果を示します:

例外の原因: spawn_link(fun() -> ok end)

捕捉されなかったもの: - nothing -

捕捉の結果: {'EXIT', <0.61.0>, normal}
プロセスは問題なく通常通り終了しました。これは catch exit(normal) の結果に似ていますが、どのプロセスが失敗したか知るためにタプルの中にPIDが追加されているところが違います。

例外の原因: spawn_link(fun() -> exit(reason) end)

捕捉されなかったもの: ** exception exit: reason

捕捉の結果: {'EXIT', <0.55.0>, reason}
プロセスが独自の理由で終了しました。この場合、補足された終了がなければプロセスはクラッシュします。 そうでなければ、上のメッセージを取得するでしょう。

例外の原因: spawn_link(fun() -> exit(normal) end)

捕捉されなかったもの: - nothing -

捕捉の結果: {'EXIT', <0.58.0>, normal}
これはプロセスが通常通り終了したのをちゃんと真似ています。 プロセスをプログラムの正常系の一部として他の例外なしに殺したいという時があるでしょう。 これはそういうときに使う方法です。

例外の原因: spawn_link(fun() -> 1/0 end)

捕捉されなかったもの: Error in process <0.44.0> with exit value: {badarith, [{erlang, '/', [1,0]}]}

捕捉の結果: {'EXIT', <0.52.0>, {badarith, [{erlang, '/', [1,0]}]}}
エラー({badarith, Reason})は try ... catch 節でキャッチされず、 'EXIT' を発生させます。 ここで、エラーは exit(reason) と全く同じように動作しますが、スタックトレースでは何が起きたかより詳細な情報が得られます。

例外の原因: spawn_link(fun() -> erlang:error(reason) end)

捕捉されなかったもの: Error in process <0.47.0> with exit value: {reason, [{erlang, apply, 2}]}

捕捉の結果: {'EXIT', <0.74.0>, {reason, [{erlang, apply, 2}]}}
1/0 の結果とほぼ一緒です。この結果は通常通りで、 erlang:error/1 はこういったことだけが出来るようになっています。

例外の原因: spawn_link(fun() -> throw(rocks) end)

捕捉されなかったもの: Error in process <0.51.0> with exit value: {{nocatch, rocks}, [{erlang, apply, 2}]}

捕捉の結果: {'EXIT', <0.79.0>, {{nocatch, rocks}, [{erlang, apply, 2}]}}
throw は決して try ... catch でキャッチされないので、エラーを発生させます。 そのエラーは代わりに EXIT になります。終了を捕捉することなくプロセスは終了します。 そうでない場合は、プロセスは終了をうまく対処します。 またこれは通常の例外も扱います。通常終了の場合: すべてはうまく終わります。例外が発生した場合: プロセスは死んで、異なるシグナルが送られます。

それから、 exit/2 があります。これは銃に等しいErlangプロセスです。 プロセスはこの関数を使って遠くから安全に別のプロセスを殺すことができます。 次にありうる呼び出し方を紹介します:

例外の原因: exit(self(), normal)

捕捉されなかったもの: ** exception exit: normal

捕捉の結果: {'EXIT', <0.31.0>, normal}
終了を捕捉しない場合、 exit(self(), normal)exit(normal) と同様に動作します。 そうでない場合は、リンク中に他のプロセスが死ぬときに受け取るのと同じ形式のメッセージを受け取ります。

例外の原因: exit(spawn_link(fun() -> timer:sleep(50000) end), normal)

捕捉されなかったもの: - nothing -

捕捉の結果: - nothing -
これは基本的に exit(Pid, normal) を呼び出しています。 このコマンドは全く役に立つことをしません。なぜなら、プロセスは引数に理由 normal を与えてもリモートから殺すことが出来ないからです。

例外の原因: exit(spawn_link(fun() -> timer:sleep(50000) end), reason)

捕捉されなかったもの: ** exception exit: reason

捕捉の結果: {'EXIT', <0.52.0>, reason}
これは外のプロセスが reason によってそれ自身を終了している呼び出し方です。 外のプロセスが exit(reason) を自分自身で呼び出すのと同じです。

例外の原因: exit(spawn_link(fun() -> timer:sleep(50000) end), kill)

捕捉されなかったもの: ** exception exit: killed

捕捉の結果: {'EXIT', <0.58.0>, killed}
驚くべきことに、メッセージは死にゆくプロセスからspawn元に向けたものに変更されています。 spawn元は kill ではなく killed を受け取ります。その理由は kill は特別な終了シグナルだからです。その詳細はまたあとで触れます。

例外の原因: exit(self(), kill)

捕捉されなかったもの: ** exception exit: killed

捕捉の結果: ** exception exit: killed
おや、見てください。これは実際捕捉できなさそうです。いろいろ確認してみましょう。

Exception source: spawn_link(fun() -> exit(kill) end)

Untrapped Result: ** exception exit: killed

Trapped Result: {'EXIT', <0.67.0>, kill}
いよいよ混乱してきました。他のプロセスが自分自身を exit(kill) で殺して終了を捕捉しない時に、自分のプロセスは理由 killed で死にます。しかしながら、終了を捕捉したときは、このようにはなりません。

ほとんどの終了理由を捕捉できる一方で、無理矢理プロセスを殺したい状況もあるでしょう: そういうプロセスの一つ例を挙げると、終了を捕捉するけど無限ループ中にいてメッセージを受け取らないようなものです。 理由 kill は捕捉できない特別なシグナルです。これはどんなプロセスでもこれを使えば確実に死んだ状態にできます。通常 kill はどんな方法もうまくいかないときの最終手段です。

../_images/trap.png

終了理由 kill は決して補足されないため、他のプロセスがこのメッセージを受け取った時には殺された状態にならなければいけません。 もし変更されなければ、リンクされた他のすべてのプロセスが代わりに死んで、また連鎖してそのまたリンクした近隣のプロセスを殺します。 死の伝達が続くのです。

これはなぜ exit(kill) が他のリンクされたプロセスから受け取った場合に殺されたよう見えるかも示しています(伝達しないようにシグナルが修正されています)が、ローカルで補足されたときには kill の用に見えます。

もしこれらすべてがややこしく感じても心配はいりません。 多くのプログラマも同じように思っています。 終了シグナルはちょっとしたいたずら野郎なのです。 幸運にも上に述べたものよりも特別なケースというのはあまりありません。 一度これらを理解してしまえば、多くのErlangの並列エラー管理を問題なく理解できます。

14.3. モニター

わかってます。プロセスを殺すのはあなたが望んでいることではないでしょう。 自分が死んでしまったからといって、環境ごと死んでもらいたくはないでしょう。 ストーカー以上にひどい野郎です。ここでは、モニターこそあなたの望んでいるものでしょう。

真面目な話、モニターは通常のリンクと2つ点で異なる特殊なリンクです:

  • 一方向である
  • スタック出来る
../_images/homer.png

モニターは、プロセスが下位のプロセスで何が起きているかを知りたいけれど、お互いが致命的にならないようにするために望まれるものです。

望まれる他の理由としては、上に挙げたように、参照をスタックできる事です。 今はパッと見これは役に立たないように思えますが、他のプロセスで何が起きているかを知る必要があるライブラリを書くときに活躍します。

見ての通り、リンクは組織的な構造というもの以上の作りになっています。 アプリケーションの構造を設計するときに、どのプロセスがどんな役割をするか決めて、何が何に依存するかを決めるでしょう。 あるプロセスは他のプロセスを監視し、あるプロセスは双子のプロセスがいないと生きていらない、などです。 この構造は通常固定されていて、事前に知ることが出来ます。 リンクはこういった時に役に立ち、それ以外ではやむを得ない場合以外は使うべきではありません。

しかし、もし呼び出すライブラリが2つや3つあって、すべてがプロセスの死活を知る必要があった場合何が起きるでしょうか。 ここでリンクを使ったら、プロセスのリンクを外すときに問題にぶつかります。 リンクはスタックできませんので、プロセスのリンクを外すときに、すべてのリンクを外して、他のライブラリから得た想定をごちゃごちゃにまとめ上げなければいけません。 これは大変よろしくありません。なので、スタック出来るリンクが必要となり、モニタがその解決策となるわけです。 モニタは個々に削除可能です。それに加えて、一方向であることはライブラリではお手軽です。 なぜなら他のプロセスが監視しているプロセスのことを気にしなくて済むからです。

モニターはどんな見た目になるのでしょうか。簡単ですが、設定してみましょう。 関数は erlang:monitor/2 で、ここでは最初の引数にプロセスのアトムを渡し、2番目の引数にpidを渡します:

1> erlang:monitor(process, spawn(fun() -> timer:sleep(500) end)).
#Ref<0.0.0.77>
2> flush().
Shell got {'DOWN',#Ref<0.0.0.77>,process,<0.63.0>,normal}
ok

監視しているプロセスが死んだ時にはいつもこのようなメッセージを受け取ります。 メッセージは {'DOWN', MonitorReference, process, Pid, Reason} です。 参照先は、そのプロセスの監視をやめること(demonitor)が出来るようにあるのです。 思い出してください、モニターはスタックできるのです。だから、1回以上モニターを切ることが出来ます。 参照によってモニターを一意に追跡することが出来ます。そして、リンクの時と同様に、プロセスを監視するときにプロセスをspawnするアトミックな関数 spawn_monitor/1-3 もあります。

3> {Pid, Ref} = spawn_monitor(fun() -> receive _ -> exit(boom) end end).
{<0.73.0>,#Ref<0.0.0.100>}
4> erlang:demonitor(Ref).
true
5> Pid ! die.
die
6> flush().
ok

この場合、監視しているプロセスがクラッシュする前に監視をやめていて、プロセスの死についてはなんの追跡もしていません。 関数 demonitor/2 も存在して、これはもうちょっと情報をくれます。 2番目の引数はオプションのリストです。オプションはたった2つで infoflush です:

7> f().
ok
8> {Pid, Ref} = spawn_monitor(fun() -> receive _ -> exit(boom) end end).
{<0.35.0>,#Ref<0.0.0.35>}
9> Pid ! die.
die
10> erlang:demonitor(Ref, [flush, info]).
false
11> flush().
ok

info オプションは、モニターを削除しようとしたときにモニターが存在するかどうかを教えてくれます。 これが10で false が返ってきた理由です。オプションに flush を使うことで、メールボックスに DOWN のメッセージがあった場合それを削除することが出来ます。 flush() は現在のプロセスのメールボックスに何もない状態にします。

14.4. プロセスに名前をつける

リンクとモニターについて分かったところで、まだ解決されていない問題があります。 次の linkmon.erl モジュールの関数を使ってみましょう:

start_critic() ->
    spawn(?MODULE, critic, []).

judge(Pid, Band, Album) ->
    Pid ! {self(), {Band, Album}},
    receive
        {Pid, Criticism} -> Criticisim
    after 2000 ->
        timeout
    end.

critic() ->
    receive
        {From, {"Rage Against the Turing Machine", "Unit Testify"}} ->
            From ! {self(), "They are great!"};
        {From, {"System of a Downtime", "Memoize"}} ->
            From ! {self(), "They're not Johnny Crash but they're good."};
        {From, {"Johnny Crash", "The Token Ring of Fire"}} ->
            From ! {self(), "Simply incredible."};
        {From, {_Band, _Album}} ->
            From ! {self(), "They are terrible!"}
    end,
    critic().

いま、このコードで擬似的にお店をまわって音楽を買うということをしてみました。 面白いと思う音楽がちょっとありますが、まったくはっきりしません。 「批評家(the critic)」というお友達を呼ぶことに決めました。

1> c(linkmon).
{ok,linkmon}
2> Critic = linkmon:start_critic().
<0.47.0>
3> linkmon:judge(Critic, "Genesis", "The Lambda Lies Down on Broadway").
"They are terrible!"

熱暴走(solar storm)により(なんとなく現実味のある理由をつけてみました)、接続が切れてしまいました:

4> exit(Critic, solar_storm).
true
5> linkmon:judge(Critic, "Genesis", "A trick of the Tail Recursion").
timeout

イライラしますね。もうアルバムの批判が聞けません。批評家を生かしておくために、基本的な ‘supervisor’ プロセスを書きましょう。これはプロセスが落ちたときに再起動することしかしません:

start_critic2() ->
    spawn(?MODULE, restarter, []).

restarter() ->
    process_flag(trap_exit, true),
    Pid = spawn_link(?MODULE, critic, []),
    receive
        {'EXIT', Pid, normal} -> % not a crash
            ok;
        {'EXIT', Pid, shutdown} -> % manual termination, not a crash
            ok;
        {'EXIT', Pid, _} ->
            restarter()
    end.

ここで、 restarter は独特なプロセスです。これは critic のプロセスを代わりに立ち上げて、もし critic が通常でない理由で死んだ場合、 restarter/0 はループして新しい critic を作ります。 必要なときに手動で critic を殺した場合を考えて {'EXIT', Pid, shutdown} という節を追加したことに注意してください。

このアプローチの問題点は critic のPidを探す方法がなく、それによって意見を聞きたい時に彼を呼べない、という点です。 Erlangにおける解決策の一つとして、プロセスに名前をつけるという方法があります。

プロセスに名前をつけることで、予測不可能なPidをアトムに置き換えることが出来ます。 このアトムはメッセージを送るときはまさにPidとして使うことが出来ます。 プロセスに名前をつけるために、 erlang:register/2 という関数を使います。 もしプロセスが死んだらプロセスは自動的に名前を失います。あるいは自分で unregister/1 を呼んで手で名前を削除することも出来ます。 登録されたプロセスの一覧を registered/0 で取得することが出来ます。あるいはより詳細なものが欲しければシェルコマンドの regs() で取得できます。 ここで、 restarter/0 関数は次のように書きなおすことが出来ます:

restarter() ->
    process_flag(trap_exit, true),
    Pid = spawn_link(?MODULE, critic, []),
    register(critic, Pid),
    receive
        {'EXIT', Pid, normal} -> % not a crash
            ok;
        {'EXIT', Pid, shutdown} -> % manual termination, not a crash
            ok;
        {'EXIT', Pid, _} ->
            restarter()
    end.

見ての通り、 register/2 は、Pidがなんであろうとも常に批評家を ‘critic’ という名前にします。 抽象関数からPidを渡す必要性をなくす必要があります。やってみましょう:

judge2(Band, Album) ->
    critic ! {self(), {Band, Album}},
    Pid = whereis(critic),
    receive
        {Pid, Criticism} -> Criticism
    after 2000 ->
        timeout
    end.

ここで、 Pid = whereis(critic) という行で receive 構文にある、批評家に対するパターンマッチのをするために、批評家のプロセス識別子を見つけています。 正しいメッセージに対して確実にマッチしてくれるので、このPidをつかってマッチしたいのです。(批評家と話しているとメッセージ500個くらい溜まってしまいます!) しかしこれは問題の原因になってしまいます。 上のコードは批評家のPidは関数の最初の2行で同じままでいることを想定しています。 しかしながら、次に説明する状況は起きても全くおかしくありません:

1. critic ! Message
                      2. critic receives
                      3. critic replies
                      4. critic dies
5. whereis fails
                      6. critic is restarted
7. code crashes

あるいは、こんなことも起きえます:

1. critic ! Message
                         2. critic receives
                         3. critic replies
                         4. critic dies
                         5. critic is restarted
6. whereis picks up
   wrong pid
7. message never matches

プロセスが他のプロセスを作ることが出来るときには正しい手順を踏まないとおかしなことが起きえます。 この例の場合は、批評家のアトムの値が複数のプロセスから見えています。 これは共有状態として知られています。ここでの問題は、批評家プロセスの値が実質同時に異なるプロセスからアクセスも修正も出来る状態になっていることです。 こういう状況は一般的に競合条件といいます。競合条件はとても危険です。なぜならイベントのタイミングによって状態が変化するからです。 あらゆる並列言語や並行言語では、ここで指しているタイミングは、プロセッサの負荷がどれくらい高いかとか、プロセスがどこにあるかとか、プログラムがどんなデータを処理しているかなど予測できない要因に依存しています。

Don’t drink too much kool-aid:

Erlangは通常、競合条件やデッドロックとは縁がなく、並行なコードが安全に書けると聞いたことがあるかもしれません。 これは多くの状況では正しいのですが、あなたのコードが本当にそんなに安全かは全く想定していません。 名前付きプロセスは並行なコードがおかしくなる多くの例の一つに過ぎません。

コンピュータ上のファイルに(変更するために)アクセスしたり、同じデータベースレコードにたくさんのプロセスから別々に更新をかける、というのも同様の例になります。

運がいいことに、もし名前付きプロセスが同じままで居続けるなら、上のコードを直すのは比較的簡単です。 代わりに、メッセージに一意な値を与えるために、参照を使います。( make_ref() で作成されます) critic/0critic2/0 に、 judge/3judge2/2 にそれぞれ書き換える必要があります:

judge2(Band, Album) ->
    Ref = make_ref(),
    critic ! {self(), Ref, {Band, Album}},
    receive
        {Ref, Criticism} -> Criticism
    after 2000 ->
        timeout
    end.

critic2() ->
receive
    {From, Ref, {"Rage Against the Turing Machine", "Unit Testify"}} ->
        From ! {Ref, "They are great!"};
    {From, Ref, {"System of a Downtime", "Memoize"}} ->
        From ! {Ref, "They're not Johnny Crash but they're good."};
    {From, Ref, {"Johnny Crash", "The Token Ring of Fire"}} ->
        From ! {Ref, "Simply incredible."};
    {From, Ref, {_Band, _Album}} ->
        From ! {Ref, "They are terrible!"}
    end,
    critic().

そして、 restarter/0critic/0 ではなく critic2/0 をspawnするように直してやります。 他の関数はうまく動作し続けるでしょう。ユーザには違いはわかりません。 これは関数の名前を付け直して、パラメータの数を変えたけれど、ユーザは実装の詳細はどう変更されたかはわからず、なぜそれが重要なのかもわからないでしょう。 ユーザが見るのはコードが簡素になったことと、関数呼び出しの際にPidを送らなくて済むようになったということしかみていません:

6> c(linkmon).
{ok,linkmon}
7> linkmon:start_critic2().
<0.55.0>
8> linkmon:judge2("The Doors", "Light my Firewall").
"They are terrible!"
9> exit(whereis(critic), kill).
true
10> linkmon:judge2("Rage Against the Turing Machine", "Unit Testify").
"They are great!"

いまや、批評家プロセスを殺しても、新しい批評家プロセスはすぐに立ち上がって、先ほどの問題は解決しています。 これが名前付きプロセスの便利な点です。 linkmon:judge/2 を登録したプロセスなしに呼び出せば、Bad Argument Errorが ! 演算子から投げられるでしょう。 これによって名前付きプロセスに結びついていないプロセスは実行できないようにしています。

Note

もし前の方の文章を覚えていたら、アトムが使える数は(多いとはいえ)有限だったことを思い出してください。 動的なアトムを作るべきではありません。これは名前付きプロセスはVMインスタンスに対して一意な重要なサービスやアプリケーション実行中は常に存在しているべきプロセスに対して予約されているべきだということを意味しています。

名前付きプロセスが必要だけれども、一時的なものだったりVMにとって一意ではない可能性がある場合、代わりにグループとして表現される必要があるということを意味しています。 動的な名前を使おうとするよりは、クラッシュした場合にリンクしてリスタートするほうが健全な選択肢です。

次の章では、実際にアプリケーションを書いて、実践のErlangでの並列プログラミングにおいて最近得た知識をご紹介します。