情報通信系学生の勉強メモ

勉強したことを書いていきます

erlangでのプロセスの使い方

プロセスとは

まず初めにプロセスとは、1つのプログラムのことをいいます。このプロセスがクラッシュするとまずいので、共有しない単独アサインロックを避ける、などの安全策がerlangには用意されています。 erlangのプロセスは他の言語でいうスレッドに近いものであり、非常に軽量。

他の言語だとメモリを共有しているものもあるようで、並行処理への評判が悪い言語もあるようです。 研究から同期処理より非同期処理の方が良さそうだとわかって来たのでerlangでは非同期アクターモデルを実装しています。

実装

erlangでは大変なことはすべてvmが面倒を見てくれるので、並列化が容易になっています。2つ目のコアを追加すれば速さ2倍、4コアなら4倍早い。速度の向上とコア数/プロセッサ数の関係を線形のスケールといいます。

どこを並列にするか

erlangは大量のデータを伴う数値計算アルゴリズムの分野は得意ではないです。チャットサーバ、Webサーバー、電話スイッチのような個別のものに並列化は使えます。ただし、どんなに並列化しても、直列の操作で一番遅いものより速くなることはない。この原理を一般化したのがアムダールの法則 です。

実際にプロセスを作ってみる

プロセスは単なる関数に過ぎない、関数を実行し、実行が終わったらプロセスは消えるだけです。

F = fun() -> 2 + 2 end.
spawn(F).

このコードを実行するとプロセス識別子が返ってきます、pidと呼ばれているやつです。 計算結果である返り値は返ってきません。プロセスは返り値を返さないからです。ここでは無名関数ですがspawnに関数を渡してあげれば、渡した関数が実行されます。

メッセージの送受信

  • メッセージの送信
self() ! hi.

erlangのシェルもプロセスから出来ています。self()の返り値はerlangシェルのpidです。 !は演算子でbangシンボルといいます。左辺はpid、右辺は送信するerlang項で、返り値も送信したerlang項です、つまりここでの返り値は hi です。

self() ! self() ! double.

これはself() ! ( self() ! double )と同じです。右から順に double が送信されるのでシェルには二回doubleが送られることになります。 また、flush関数を使うと受信したメッセージを見れます。

  • メッセージの受信

receive式を知らなければメッセージの受信は出来ません、receive文はcase of文と同じ用に、受信したメッセージを変数に束縛します。

receive文ではガードも使えます。receive文がある関数をspawn/3に渡すとプロセスはreceive文のところでメッセージを受信するまで待機します。メッセージを受信したらあとはreceive文のパターンマッチに従って実行されます。

 

receive文を使ったプロセス

bookshelves(BookList) ->
  receive
    {From, {store,Book}} ->
        From ! {self(),ok},
        bookshelves([Book|BookList]);
    {From, {take,Book}} ->
        case lists:member(Book,BookList) of
            true ->
                From ! {self(),{ok,Book}},
                lists:delete(Book,BookList);
            false ->
                From ! {self(), nothing},
                bookshelves(BookList)
        end;
    terminate ->
        ok
  end.

本棚を表す関数を書いてみました。この関数は本棚に本を置くことと、本棚から取り出すことしか出来ません。 それぞれのガードの後に再帰させているのはプロセスが常にメッセージを受け取れるようにするためです。再帰させなかったらそのままプロセスは終了してしまいます。

また、プロセスを終了させたい時はterminateを送信するとプロセスは終了するようになっています。 下記にシェル内での実行結果を書きます。

Eshell V7.0  (abort with ^G)
1> c(kitchen).
{ok,kitchen}
2> Pid = spawn(kitchen , bookshelves , [[math_book]]).
<0.40.0>
3> Pid ! {self(), {store, sugoi_erlang}}.
{<0.33.0>,{store,sugoi_erlang}}
4> Pid ! {self(), {take, sugoi_erlang}}.
{<0.33.0>,{take,sugoi_erlang}}

storeでsugoi_erlangという本を置いて、その後takeで取り出しているのがわかると思います。