Create a gist now

Instantly share code, notes, and snippets.

What would you like to do?

プログラミングErlang - 並行プログラミング

8章あたり

並行プログラミングの基本処理

Eralngで並行プログラミングするには、以下の3つプリミティブ処理を覚えればいい

プロセスの作成

Pid = spawn(Fun)
  • Fun を評価する新しい並行プロセスを作る。
  • 新しいプロセスは呼び出し側と並列に動作する。
  • spawn はPid(process identifier(プロセス識別子))を返す。
  • Pid はプロセスにメッセージを送信するときに使う。

プロセスにメッセージを送る

Pid ! Message
  • 識別子がPid のプロセスにMessage を送信する。
  • メッセージの送信は非同期に行われる。
  • 送信側は結果を待たずに次の処理に移る。
  • ! は送信演算子と呼ばれる。
  • Pid ! M の値はM と定義されている。

つまりメッセージ送信プリミティブ! は、送信したメッセージ自体を返す。
このためPid1 ! Pid2 ! ... ! M は、メッセージM をプロセスPid1 ,Pid2 . . . のすべてに送信する。

プロセスがメッセージを受け取る

receive
    Pattern1 [when Guard1] ->
        Expressions1;
    Pattern2 [when Guard2] ->
        Expressions2;
    ...
    PatternN [when GuardN] ->
        ExpressionsN
end
  • メッセージを受信すると、receive ... end内で処理される
  • メッセージは上からPatternXと照合していき、一致すれば評価する
  • どのパターンも一致しなければ、プロセスは次のメッセージを待つ。
  • 受信文で使われているパターンやガードの構文と意味は、関数を定義でに使うパターンやガードと同じ

簡単な例

・area_server0.erl

-module(area_server0).
-export([loop/0]).

loop() ->
    receive
        {rectangle, Width, Ht} ->
            io:format("Area of rectangle is ~p~n", [Width * Ht]),
            loop();
        {circle ,R} ->
            io:format("Area of circle is ~p~n", [3.14 * R * R]),
            loop();
        Other ->
            io:format("I don't know what the area of a ~p is ~n", [Other]),
            loop()
    end.

・Eshell

1> c(area_server0).
{ok,area_server0}
2> Pid = spawn(fun area_server0:loop/0).
<0.67.0>
3> Pid ! {rectangle, 6, 10}.
Area of rectangle is 60
{rectangle,6,10}
4> Pid ! {circle, 23}.
Area of circle is 1661.06
{circle,23}
5> Pid ! {triangle, 2, 4, 5}.
I don't know what the area of a {triangle,2,4,5} is 
{triangle,2,4,5}

クライアントサーバ

Erlangのクライアントサーバアーキテクチャは、一般的なネットワークを隔て
クライアント複数個、サーバ1つとする考えとは異なり以下の特徴がある。

  • クライアントとサーバとは別々のプロセス
  • クライアントとサーバの通信にはメッセージパッシングを利用
  • クライアントとサーバは同じマシン、別のマシンで動作する場合がある
  • クライアントが常にサーバを開始させ、要求をサーバに送信する
  • サーバは要求された計算を実行して、クライアントに応答を送信する
  • クライアントとサーバの定義
    • 最初に要求を送信するプロセスのことをクライアントと呼ぶ
    • 要求を受信して応答を送信するプロセスのことサーバと呼ぶ

以下のコードでは

  • rpc/2のself()がクライアントのプロセスにあたる
  • start/0で生成するPidとloop/0内のself()がサーバプロセスにあたる

・area_server1.erl

-module(area_server1).
-export([start/0, area/2]).

start() -> spawn(fun loop/0).

area(Pid, What) ->
    rpc(Pid, What).

rpc(Pid, Request) ->
    Pid ! {self(), Request},
    receive
        {Pid, Response} -> Response
    end.

loop() ->
    receive
        {From, {rectangle, Width, Ht}} ->
            From ! {self(), Width * Ht},
            loop();
        {From, {circle ,R}} ->
            From ! {self(), 3.14 * R * R},
            loop();
        {From, Other} ->
            From ! {self(), {error, Other}},
            loop()
    end.

・Eshell

1> c(area_server1).
{ok,area_server1}
2> Pid = area_server1:start().
<0.63.0>
3> area_server1:area(Pid, {rectangle, 10, 8}).
80
4> area_server1:area(Pid, {cicrle, 4}).
{error,{cicrle,4}}
5> area_server1:area(Pid, {circle, 4}).
50.24

タイムアウト付きの受信にしたい場合

receive式に入ってから、Timeミリ秒内に一致するメッセージが到着しなければ
プロセスはメッセージを待つのをやめてExpressionsを評価する

receive
    Pattern1 [when Guard1] ->
        Expressions1;
    Pattern2 [when Guard2] ->
        Expressions2;
    ...
after Time ->
    Expressions
end

登録済みプロセス

プロセスにメッセージを送信するために事前にプロセスのPIDを公開しておいて、
どのプロセスでもそのプロセスと通信できるようにするための方法が用意されている。
このようなプロセスを登録済みプロセスという。

登録済みプロセスに関するBIF は4つある:

  • register(AnAtom, Pid) プロセスPidをAnAtomという名前で登録する。
    AnAtomという名前でプロセスが既に登録されている場合は登録は失敗する。

  • unregister(AnAtom) AnAtom に対応する登録を削除する。
    ※ 登録済みプロセスが死ぬと、そのプロセスの登録は自動的に解除される。

  • whereis(AnAtom) -> Pid | undefined AnAtom が登録されているかどうか調べる。
    プロセス識別子Pid を返すが、AnAtomに対応するプロセスが登録されていない場合はアトムundefinedを返す。

  • registered() -> [AnAtom::atom()] システムに登録されているすべてのプロセスのリストを返す。

1> Pid = spawn(fun area_server0:loop/0).
<0.51.0>
2> register(area, Pid).
true
%%プロセスの名前をいったん登録すれば、次のようにメッセージを送信できる
3> area ! {rectangle, 4, 5}.
Area of rectangle is 20
{rectangle,4,5}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment