Erlang World


top > concurrent programming > get response

Get Response

プロセスに返信させる方法

この節では特に新しいことは学びませんが、メッセージ送信で知っておくと便利な手法を学びます。

まずはじめに、プロセスに返信させる方法を学びます。 これは特に難しくはなく、通信先に自分の識別子を伝えることで行なわれます。 よく使う手法なので慣れておいて下さい。

手順のイメージは以下のようになります。

プロセスとして呼び出される関数
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によって新しく作り出されています。

「逐次処理で繋がっているのが同じプロセスである」ということに注意して下さい。


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