この節では一つのノード内での分散プログラミングを行ないます。
並列プログラミングは一つのノード(Erlang Run-time)内で複数のプロセスを実行しました。 これに対して、分散プログラミングは複数のノード内でプログラミングを行ないます。
今までErlangを起動する際に、「 erl 」とやって起動し、Erlangのシェルを起動していましたが、 こんどはそれを複数個利用するということです。
ノード(Erlangノード)とは一つのErlangランタイムシステムが実行している環境と捉えて下さい。
ホストは論理的な一つのマシンのことです。通常は一つのハードウェア上に起動されているOSは一つなので、 PC本体を一つのホストと捉えて構いません。
今回は一つのホストの上に、複数のErlangノードを作成します。
そのためには今までとは違い、Erlangノードの一つ一つに名前をつけなければなりません。 名前がついていないと、ノード同士の区別が出来ないからです。
それでは実際に立ち上げてみます。
$ erl -sname mario
Erlang (BEAM) emulator version 5.6.1 [source] [smp:2] [async-threads:0] [kernel-poll:false]
Eshell V5.6.1 (abort with ^G)
(mario@ada)1>
Erlangシェルの立ち上げ方が今までと少し異なっています。 同一ホスト内に名前を指定してノードを立ち上げるには以下の書式を使います。
erl -sname 名前
上の例では、ノード名は mario となっています。
MacOSXにおけるErlangシェルのプロンプトの書式は次のようになっています。
(ノード名@マシン名)行番号>
私のマシン名が ada なので、上のような結果になっています。
プロンプトの出力はOSやErlangのバージョンなどによって多少違いがあるようです。
今までは自分のノード内の関数を呼び出していましたが、今回は別のノードにある関数を呼び出したいと思います。
これは「別ノードにある関数を呼び出す」役割を持つ関数である rpc:call() を使います。
書式は erlang:apply(module,function,arity) に非常に良く似ています。
rpc:call(node, module, function, arity)
という書式になっています。
実験に使うコードです。
-module(node_test1).
-export([add/2, print/1]).
add(X,Y) ->
X + Y.
print(X) ->
io:format("out: ~p~n",[X]).
それでは、実際に実験をしてみましょう。
mario と luigi というノードを立ち上げます。
$ erl -sname mario
(mario@ada)1>
$ erl -sname luigi
(luigi@ada)1>
ノードluigi が ノードmarioに上の関数を実行させます。
(luigi@ada)1> rpc:call(mario@ada, node_test1, add, [3,5]).
8
きちんと動作しているようです。マリオのほうのシェルには何も表示されていません。
(mario@ada)1>
次にluigiノードからmarioノードへ、エラーとなる命令を出してみます。
(luigi@ada)2> rpc:call(mario@ada, node_test1, add, [dog,cat]).
{badrpc,{'EXIT',{badarith,[{node_test1,add,2}]}}}
luigiノードでエラーが出力されています。marioノードには何も表示されていません。
次にmarioノードのprint関数を使ってみます。
(luigi@ada)3> rpc:call(mario@ada, node_test1, print, [hello]).
out: hello
ok
これもluigiノードに出力されました。
別のプロセスにサーバを作成して実験をしてみます。
-module(node_test2).
-export([start1/0, start2/0, loop/0]).
-export([call_add1/2, call_add2/3]).
start1() ->
spawn(?MODULE, loop, []).
start2() ->
register(server, spawn(?MODULE, loop, [])).
call_add1(X,Y) ->
server ! {self(), add, {X,Y}},
receive
Reply ->
Reply
after
3000 ->
io:format("timeout~n",[])
end.
call_add2(Node, X, Y) ->
{server, Node} ! {self(), add, {X, Y}},
receive
Reply ->
Reply
after
3000 ->
io:format("timeout~n",[])
end.
loop() ->
receive
{From, add, {X,Y}} ->
From ! (X + Y);
Other ->
io:format("receive: ~p~n", [Other]),
exit(receive_unexpected_message)
end,
loop().
先ほどと同じく、luigiノードからmarioノードの関数を呼び出します。
(luigi@ada)4> Pid = rpc:call(mario@ada, node_test2, start1, []).
<5058.43.0>
(luigi@ada)5> Pid ! {self(), add, {3,5}}.
{<0.37.0>,add,{3,5}}
(luigi@ada)6> receive X -> X end.
8
自分のノードに作ったサーバプロセスと全く同じように働いてくれていることがわかります。
次にregister関数を試すために、start2/0を使ってPidの登録を行ないます。 そしてrpc_callを使ってサーバに対してアクセスを行なってみたいと思います。
(luigi@ada)1> rpc:call(mario@ada, node_test2, start2, []).
true
(luigi@ada)3> rpc:call(mario@ada, node_test2, call_add, [3,5]).
timeout
ok
(luigi@ada)2> whereis(server).
undefined
どうやら、register関数を使っても登録されていないようですね。 したがって、きちんとサーバに対して送信できなかったため、返信が来ずにタイムアウトとなっているようです。
register関数は実はmarioノードの中で働いています。
(mario@ada)1> whereis(server).
<0.43.0>
register関数は、実行されている環境においてatomとPidを対応付けるのでした。 この関数を使用したノードは mario@ada であったため、 luigi@ada では使えなかったのです。
他のノードの登録済みプロセスに対してメッセージを送信するには、次のようにする必要があります。
{登録済みプロセス名, ノード名} ! メッセージ
上の例では {server, mario@ada} ! Message とする必要があります。
この呼び出し方を使っている rpc:call_add2 を使ってみます。
(luigi@ada)1> rpc:call(mario@ada, node_test2, start2, []).
true
(luigi@ada)2> node_test2:call_add2(mario@ada, 3,4).
7
どうやら無事に動いてくれたようです。
プロセスの生成
spawn(Node, Fun)
(luigi@ada)7> Fun = fun() ->
(luigi@ada)7> receive Y -> io:format("rec: ~p~n",[Y]) end
(luigi@ada)7> end.
#Fun<erl_eval.20.67289768>
(luigi@ada)8> Pid = spawn(mario@ada, Fun).
<5058.45.0>
(luigi@ada)9> Pid ! hello.
hello
rec: hello
spawn(Node, Module, Function, Args)
(luigi@ada)3> Pid = spawn(mario@ada, node_test2, loop, []).
<5058.44.0>
リンク付きプロセス生成。単独のリンクとモニターは並列処理の場合と同じくPidを登録するだけです。
spawn_link(Node, Fun)
spawn_link(Node, Module, Function, Args)
ノードのモニター
monitor_node(Node, Flag)
ノードNodeを監視する。
Flagはtrueならばモニターを有効にし、falseなら無効にする。
モニターしているNodeが起動した場合に{nodeup, Node}
切断された場合に{nodedown, Node}を返す。
ノードの状態を得る
node()
自分のノードの名前を得る。
(luigi@ada)10> node().
luigi@ada
nodes()
分散Erlangに参加しているノード名を返す。
(luigi@ada)11> nodes().
[mario@ada]
is_alive()
自ノードが分散Erlangに参加していればtrueを返す。
参加していなければfalseを返す。
(luigi@ada)12> is_alive().
true
node(Pid)
Pidを持つノードを返す。
誰も持っていなければ nonode@nohost を返す。
(luigi@ada)14> Pid = spawn(mario@ada, fun() -> receive Y -> Y end end).
<5058.46.0>
(luigi@ada)15> node(Pid).
mario@ada