Erlang World


top > distributed programming > in host

Distributed Programming in a host

ノード

この節では一つのノード内での分散プログラミングを行ないます。

並列プログラミングは一つのノード(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ノードに出力されました。

サンプル2

別のプロセスにサーバを作成して実験をしてみます。

-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

どうやら無事に動いてくれたようです。

BIF

プロセスの生成

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


Yuichi ITO. All rights reserved.
mail to : ad
inserted by FC2 system