この節では特に新しいことは学びませんが、メッセージ送信で知っておくと便利な手法を学びます。
まずはじめに、プロセスに返信させる方法を学びます。 これは特に難しくはなく、通信先に自分の識別子を伝えることで行なわれます。 よく使う手法なので慣れておいて下さい。
手順のイメージは以下のようになります。
プロセスとして呼び出される関数
loop() ->
receive
{From, Data} ->
NewData = Dataを処理,
From ! NewData,
loop()
end.
呼び出し側
Pid = spawn(module,loop,[]),
Pid ! {自分のPid, Data},
receive
X ->
X
end.
受信プロセス loop/0 の中に From ! NewData という部分がありますね。 送信プロセスのPid宛に返信をしているのです。
また、送信プロセスの中にも受信処理が書いてありますね。 これは loop/0 からの返信を受け取るためのものです。
それでは、サンプルを通して学んでみましょう。
-module(math_server).
-export([start/0]).
start() ->
spawn(fun() -> loop() end).
loop() ->
receive
{add, From, X, Y} ->
Z = X + Y,
From ! Z,
loop();
{multi, From, X, Y} ->
Z = X * Y,
From ! Z,
loop();
{stop, From} ->
io:format("stop server~n",[]),
From ! {ok,stop_server};
Other ->
io:format("~p is not supported. stop server~n",[Other]),
killed
end.
実行してみます。
なお、自分のプロセス識別子を得るためには 関数self() を使います。
1> Pid = math_server:start().
<0.33.0>
2> Pid ! {add, self(), 5, 3}.
{add,<0.31.0>,5,3}
3> receive X -> X end.
8
4> Pid ! {multi, self(), 80, 120}.
{multi,<0.31.0>,80,120}
5> receive Y -> Y end.
9600
6> Pid ! {stop, self()}.
stop server
{stop,<0.31.0>}
7> receive Z -> Z end.
{ok,stop_server}
うまく動いてくれているようです。
先ほどのプログラムは多少見苦しい部分がありました。
特に、
3> receive X -> X end.
8
という部分があまり上品だとは呼べません。
次に、先ほどのプログラムを改良して、プロセス間の通信を隠蔽化します。
別のプロセスを使っているのですが、まるで自分のプロセスで関数を扱っているかのように見えます。
それでは、プログラムを見てみましょう。
-module(math_server2).
-export([start/0, call/2]).
start() ->
spawn(fun() -> loop() end).
call(Pid, Message) ->
Pid ! {self(), Message},
receive
Return ->
Return
end.
loop() ->
receive
{From , {add, X, Y}} ->
Z = X + Y,
From ! Z,
loop();
{From, {multi, X, Y}} ->
Z = X * Y,
From ! Z,
loop();
{From, stop} ->
io:format("stop server~n",[]),
From ! {ok,stop_server};
{From, Other} ->
io:format("~p is not supported. stop server~n",[Other]),
From ! {get_unkown_type, server_killed}
end.
loop/0関数は変わっていないのですが、call/2という関数が増えていますね。
call/2はメッセージの送信と受信を行なうために作成した関数です。
実行してみましょう。
1> Pid = math_server2:start().
<0.33.0>
2> math_server2:call(Pid, {add,3,9}).
12
3> math_server2:call(Pid, {multi,8,11}).
88
4> math_server2:call(Pid, stop).
stop server
{ok,stop_server}
別のプロセスでの処理を、まるで関数で扱っているかのように見えますね。 これは、プロセス間の通信を関数の中に隠蔽してあげているからです。
注意して欲しいのは
loop/0とcall/2は別のプロセスから利用されていることです。
オブジェクト指向と違い、一つのモジュールの中にある関数同士に関連性はありません。 この場合は、call/2はシェルのプロセスで利用されていて、loop/0はspawnによって新しく作り出されています。
「逐次処理で繋がっているのが同じプロセスである」ということに注意して下さい。