M12i.

学術書・マンガ・アニメ・映画の消費活動とプログラミングについて

Erlangチュートリアルを読む (4)

並行プログラムの章の中盤です。原典は、“Getting Started with Erlang User's Guide - Version 5.8.4 - May 24 2011”(2011/07/09取得)です。

******************************

3.4分散プログラミング

さて、今度はこのping pongプログラムを、別々のコンピュータ上の“ping”と“pong”からなるものに書き換えてみましょう。その前に、いくつか済ませておかないといけないことがあります。分散されたEralng実装は、別のコンピュータ上のErlangシステムへの認証されていないアクセスを阻止するための、基本的なセキュリティ・メカニズムを提供しています(*manual*)。互いにメッセージをやり取りするErlangシステムは、同じマジック・クッキー〔互いの身元を証明するコード〕を持っている必要があります。このクッキーを取得するためのもっとも簡単な方法は、互いに通信しあうErlangシステムを稼働させようとしているすべてのマシン上で、.erlang.cookieという名前のファイルをホームディレクトリに格納しておくというものです(Windowsではホームディレクトリは$HOME環境変数によって指されるディレクトリ〔フォルダー〕です。この変数は自分で作成する必要があります。LinuxもしくはUnixではその必要はなく、cdコマンドを引数なしで実行したときにカレントとなるディレクトリに.erlang.cookieを作成するだけです)。.erlang.cookieファイルは同じアトム表現の行を内容としなくてはなりません。例えば、LinuxUnixのOSシェルでは:

$ cd
$ cat > .erlang.cookie
this_is_very_secret
$ chmod 400 .erlang.cookie

chmodコマンドは.erlang.cookieファイルにそのオーナーしかアクセスできないように設定します。これは必須です。

他のErlangシステムとやり取りするErlangシステムを開始するには、次のように名前を指定する必要があります。

$ erl -sname my_name

この点については、後ほどより詳細に見ていくことになります(*manual*)。分散Erlangシステムを試してみたいが動かすことのできるコンピュータが1台しかないという場合、同じコンピュータ上で異なる名前を振った2つの独立したErlangシステムを動かすことができます。あるコンピューター上で動くそれぞれのErlangシステムのことを、Erlangノード〔Erlang node〕と呼びます。

(注意:erl –snameはすべてのErlangノードが同じIPドメインで稼働していることを想定しています。そしてIPアドレスの最初のコンポーネントしか使用できません。もし異なるドメイン上でノードを使用したいのであれば、-nameを使用します。しかしIPアドレスは省略なしのフルネームで記述しないといけません(*manual*))。

ここに2つの独立したノード上で動くように修正されたping pongの例があります:

-module(tut17).
-export([start_ping/1, start_pong/0,  ping/2, pong/0]).
ping(0, Pong_Node) ->
    {pong, Pong_Node} ! finished,
    io:format("ping finished~n", []);
ping(N, Pong_Node) ->
    {pong, Pong_Node} ! {ping, self()},
    receive
        pong ->
            io:format("Ping received pong~n", [])
    end,
    ping(N - 1, Pong_Node).
pong() ->
    receive
        finished ->
            io:format("Pong finished~n", []);
        {ping, Ping_PID} ->
            io:format("Pong received ping~n", []),
            Ping_PID ! pong,
            pong()
    end.
start_pong() ->
    register(pong, spawn(tut17, pong, [])).
start_ping(Pong_Node) ->
    spawn(tut17, ping, [3, Pong_Node]).

gollumkoskenという2つのコンピュータを持っているとしましょう。koskenノードでpingを実行し、gollumノードでpongを実行します。

koskenノードでは(LinuxUnixシステム上では):

kosken> erl -sname ping
Erlang (BEAM) emulator version 5.2.3.7 [hipe] [threads:0]
Eshell V5.2.3.7  (abort with ^G)
(ping@kosken)1>

一方gollumノードでは:

gollum> erl -sname pong
Erlang (BEAM) emulator version 5.2.3.7 [hipe] [threads:0]
Eshell V5.2.3.7  (abort with ^G)
(pong@gollum)1>

さてgollumノード上で“pong”プロセスが起動します:

(pong@gollum)1> tut17:start_pong().
true

そしてkoskenノード上で“ping”プロセスを起動します(次のコードから、start_ping関数の引数は“pong”プロセスを実行しているErlangシステムの名前であることがわかります):

(ping@kosken)1> tut17:start_ping(pong@gollum).
<0.37.0>
Ping received pong
Ping received pong 
Ping received pong
ping finished

ping pongプログラムが動いていることがわかります。では“pong”プロセスの側〔のノードの出力〕を見てみましょう:

(pong@gollum)2>
Pong received ping                 
Pong received ping                 
Pong received ping                 
Pong finished                      
(pong@gollum)2>

tut17〔モジュール〕のコードを見てみましょう。pong関数はそれ自体としては変化していません:

{ping, Ping_PID} ->
    io:format("Pong received ping~n", []),
    Ping_PID ! pong,

このコードは“ping”プロセスが実行されているのがどのノードであるかに関わりなく同じように動作します。ErlangプロセスIDはそのプロセスがどこで実行されているのかという情報を内包しているので、プロセスIDさえ分かれば、“!”演算子はメッセージの送信に使用できます。そのプロセスが同じノードであるか別のノードであるか関係なしにです。

ちがいは別のノード上の〔名前で〕登録されたプロセスにメッセージを送信する方法にあります:

{pong, Pong_Node} ! {ping, self()},

登録された名前そのものを使うかわりに、{registered_name,node_name}という形式のタプルを使っています。

今回の例では、2つの独立したErlangノード上のシェルで、“ping”と“pong”の2つのプロセスを起動させました。spawn関数は異なるノード上でプロセスを起動することもできます。次の例はまたしてもping pongプログラムですが、今度は別ノード上の“ping”を起動してみます:

-module(tut18).
-export([start/1,  ping/2, pong/0]).
ping(0, Pong_Node) ->
    {pong, Pong_Node} ! finished,
    io:format("ping finished~n", []);
ping(N, Pong_Node) ->
    {pong, Pong_Node} ! {ping, self()},
    receive
        pong ->
            io:format("Ping received pong~n", [])
    end,
    ping(N - 1, Pong_Node).
pong() ->
    receive
        finished ->
            io:format("Pong finished~n", []);
        {ping, Ping_PID} ->
            io:format("Pong received ping~n", []),
            Ping_PID ! pong,
            pong()
    end.
start(Ping_Node) ->
    register(pong, spawn(tut18, pong, [])),
    spawn(Ping_Node, tut18, ping, [3, node()]).

pingと呼ばれるErlangシステム(“ping”プロセスとは別です〔実際、“ping”プロセスはこのあとstart関数内で生成されます〕)がkoskenノードの上ですでに動いているとしましょう。gollumで次ぎのようにします:

(pong@gollum)1> tut18:start(ping@kosken).
<3934.39.0>
Pong received ping
Ping received pong
Pong received ping
Ping received pong
Pong received ping
Ping received pong
Pong finished
ping finished

すべての出力をgollum上で得ていることに注意してください。ioシステムはプロセスが生成された場所を知り、すべての出力をその生成元に送るからです。

******************************

原典は、“Getting Started with Erlang User's Guide - Version 5.8.4 - May 24 2011”(2011/07/09取得)です。

原点では続いて「3.5より大きなプログラム例」が続くのですが、気力が続かないので(それにしてもブログに掲載するにも無理があるコード量なので)とりあえずここまでとします。