Erlang 基礎ポイント7 - 分散処理

久しぶりのErlangですね。
今日は分散処理について勉強したいと思います。

Erlangで分散処理?

今までは一つのサーバで一つのErlangシェルを立ち上げて勉強しましたが、
実はErlang複数のサーバがネットワークを作って、
大規模な動作をするのに適した仕組みを持っています。
簡単に複数のサーバをつなげて何かができるってことは楽しいですね。

Erlangノード

今マシンで動いている一つのErlangプログラムをErlangノードといいます。
正確にいうと、Erlangプログラムを動かしているErlang仮想マシンをいいます。
JavaJVMのようなものが立ち上がっていると考えればいいでしょう。

JVMは同じマシンで複数実行できますね。
同様にErlangも同じマシンで複数実行できます。
同じマシンでErlangプログラム2個実行した?
つまり、erlコマンドを2個実行したといったら、
"このマシンにはErlangノードが2つ立ち上がっている"といえます。

但し、erlコマンド実行し、Erlangプロセスが終了せず、ずっと動いていなければ、
立ち上がっているとはいえません。あくまで実行中のことが前提です。

Erlangノードについて説明したので、分散処理ノードの形態について
説明します。

分散処理ノードの形態

Erlangノードがどのネットワークのどのマシンにあるかによって、
分散処理ノードの形態は以下のように三つあります。

Erlangノードが同じマシン上にある

一つのマシン上に複数Erlangを実行して分散処理を行います。
一つマシンのリソースを分けて処理をするので、あまりパワーは出せないでしょう。
テスト用に分散処理を確認したい時にいいと思います。

Erlangノードがプライベートネットーワーク(LAN)上にある

社内ネットワークなので、セキュリティが安全ですし、複数のマシンを有効に活用できるので、
これが一番実用性があるのではないかと思います。

Erlangノードがインターネット上にある

インターネット上にある無数のマシンを束ねられることは魅力的ですが、
セキュリティの確保が難しいと思います。
でも、セキュリティにあまり気をつけなくてもいいすごく時間がかかる計算を
小さい処理単位に分けて、インターネット上のマシンで計算して結果を結合するといった用途に
合うかもしれない...と思います。

サンプル

準備

ここでは、プライベートネットーワーク(LAN)上にErlangノードをおくことを前提で、サンプルを作ってみます。
私は実PCを一台しか持っていないので、無料の仮想化ソフト「VirtualBox」を利用し、
Ubuntu 12.05が入った仮想PCを二台作ってから、サンプルを作成及び実行します。

Ubuntu 12.05はHomepage | Ubuntu Japanese Teamでダウンロードできます。
VirtualBox用のイメージが既にあるので、自分でインストールしなくていいです。
仮想PCを二つ作るにはVirtualBoxのクローン機能を使って、入手したイメージをコピーすればすぐです。

以下のように仮想PCを二つ作りました。
f:id:jeongman7:20140831161913j:plain

しかし、ソースをそれぞれの仮想PCで編集すると不便なので、
SSHで各仮想PCに接続してソースの編集を行います。
SSHクライアントはRLoginを使いました。
f:id:jeongman7:20140831200919j:plain

理解しやすくするため、サンプルは、echoサーバのようなものにします。
具体的には送信側がメッセージを受信側に送ると、
受信側は受信したメッセージに特定文字列を付加して受信側に返すものです。

以下、ソースです。

-module(echo).
-export([start/0, send/1]).

start() -> register(echo, spawn(fun() -> loop() end)).

send(Msg) ->
    echo ! {self(), {server, Msg}},
    receive
        {client, Res} ->
            Res
    end.

loop() ->
    receive
        {From, {server, Msg}} ->
            From ! {client, string:concat("Echo response : ", Msg)},
            loop()
    end.

分散処理を行う前にローカルでstart()関数とsend()関数を呼び出して動作確認をしてみましょう。

6> duke1@duke1-VirtualBox:~/erlang/blog$ erl
Erlang R14B04 (erts-5.8.5) [source] [rq:1] [async-threads:0] [kernel-poll:false]

Eshell V5.8.5  (abort with ^G)
1> c(echo).
{ok,echo}
2> echo:start().
true
3> echo:send("Hello Erlang").
"Echo response : Hello Erlang"
4> echo:send("Hello Erlang").
"Echo response : Hello Erlang"
5> echo:send("Hello Erlang1").
"Echo response : Hello Erlang1"
6> 

問題ないですね。
次は別々の仮想PCでErlangを動かして、分散処理をやってみます。

別々のマシンで分散処理

分散処理するすべてのErlangノードは同じバージョンのErlangと同じプログラムコードを
持つ必要があります。Erlangのバージョンを合わせるためにはそれぞれのマシンに同じバージョンのErlangをインストールするしかないでしょう。
しかし、プログラムコードの共有方法は以下のように色々あるようです。

  1. いちいちプログラムコードをそれぞれのサーバにコピーする。
  2. NFSなどの共有ディスクを使う。
  3. コードサーバを利用する。
  4. Erlangシェルのコマンドnlを使う。

ここでは、NFSを利用して上記のechoプログラムを2台の仮想PCで共有します。
NFSについては、ググればすぐ出ます。
機会があれば、コードサーバに関してブログを書いてみたいと思います。

まず、仮想PC 1号機・2号機のhostsファイルにそれぞれのホスト名とIPアドレスの対応を追加します。
例えば、1号機をvpc1.com(192.168.56.101)、2号機をvpc2.com(192.168.56.103)とするなら、以下のようにします。

192.168.56.101  vpc1.com
192.168.56.103  vpc2.com

次に仮想PC 1号機で以下のようにerlを起動します。
(物理PCや仮想PCに関わらず、ネットワーク状況によって以下の起動オプションでサンプルが動かないかも知れません。私の環境でしか確認していません。)

$ erl -name echoserver@vpc1.com -setcookie echocookie
% -name サーバ名 : 呼出側がこのマシンで動くErlangプログラムを実行する時、識別のため、必要です。
%                  @の前にはErlangノード同士で識別する名前を書きます。
%                  @の後ろはhostsファイルに追加した名前(vpc1.com)を書きます。
% -setcookie クッキー名 : 分散処理をするErlang同士の認証に使われます。適当でいいと思いますが、すべてのErlangノードは同じクッキー名を使う必要があります。

Erlang R14B04 (erts-5.8.5) [source] [rq:1] [async-threads:0] [kernel-poll:false]

Eshell V5.8.5  (abort with ^G)
(echoserver@duke1-VirtualBox)1> c(echo). % コンパイルし、
{ok,echo}
(echoserver@duke1-VirtualBox)2> echo:start(). % エコーサーバを起動します。
true

仮想PC 2号機では、以下のようにerlを起動します。

erl -name echoclient@vpc2.com -setcookie echocookie
Erlang R14B04 (erts-5.8.5) [source] [rq:1] [async-threads:0] [kernel-poll:false]

Eshell V5.8.5  (abort with ^G)
(echoclient@vpc2.com)1> c(echo). % 2号機でもコンパイルします。
{ok,echo}

準備ができました。
仮想PC 2号機で仮想PC 1号機に対してリモート呼出をしてみましょう。

(echoclient@vpc2.com)1> rpc:call(echoserver@vpc1.com, echo, send, ["Hello"]). 
"Echo response : Hello"

できました!
他のマシンにあるErlangプログラムを呼び出す時は、rpcモジュールのcall関数を使います。

rpc:call(相手側のErlangノードのnameに指定した名前@相手側のErlangノードが入っているマシンのホスト名, モジュール名, 関数名, [引数1, 引数x...]). 

他の言語にもあるSOAPやRPCのようで、大したものではないように見えるかも知れませんが、
プログラムや手順が簡単で無駄がないのが分かります。
この簡潔さなら、本来の処理にもっと集中できるのではないでしょうか。

追加(2014/09/26)

rpc:callを使うと、呼び出しが煩雑ですが、
以下のようにsend関数を修正すれば、もっと簡単にリモート呼出ができます。

send(Msg, Receiver) ->
    Receiver ! {self(), {server, Msg}},
    receive
        {client, Res} ->
            Res
    end.

仮想PC 2号機から仮想PC 1号機への呼出は以下のようにすればいいです。

(echoclient@vpc2.com)2> echo:send("Hi VPC1", { echo, 'echoserver@vpc1.com' }).
"Echo response : Hi VPC1"
(echoclient@vpc2.com)3> 

自分自身へはただのechoでいいですね。
もちろん、自分のところでもecho:start()を呼び出す必要があります。

(echoclient@vpc2.com)3> echo:start().
true
(echoclient@vpc2.com)4> echo:send("Hi VPC1", echo).                           
"Echo response : Hi VPC1"

要は、メッセージを送る時、プロセス名 ! メッセージを使いますが、
メッセージの受け取り側がリモートの場合は、プロセス名が{ プロセス名, 'サーバ名' }のように
タプルになるということです。

今日はここまで。

他にソケット通信やファイル、OTPなどがありますが、
これらはAPIの使い方みたいなものなので、基礎ポイントでは取り上げないつもりです。
別のタイトルで勉強しようかと思います。

次回はちょっと面白そうな...ErlangJavaの接続についてやってみたいと思います。

参考

プログラミングErlang

プログラミングErlang

複数のファイルの名前を一気に変更するバッチスクリプト。

ファイル名が似ている複数のファイルの名前を変更したいが、
ファイルの数が多くて面倒なことがありますね。

そういう時はツールをダウンロードして解決したり、
自分の得意なプログラミング言語
プログラムを作って解決したりします。

解決する方法は色々あると思いますが、
Windowsの場合、バッチスクリプトを使えば、
簡単です。

以下のバッチスクリプトを、拡張子をbat(又はcmd)に
して保存し、実行すれば、簡単に複数のファイルの名前を
一気に変更することができます。

但し、変数の設定を先に行わなければなりません。
下記スクリプトから、pattern, filename, ext, targetを変更する必要があります。

以下のスクリプトの場合、
sample 01 document.txt
sample 02 document.txt
sample 03 document.txt
のようなファイルを
test 01.txt
test 02.txt
test 03.txt
のように変更してくれます。

詳細はスクリプト内のコメント(rem部分)を参照して下さい。
スクリプトの修正が必要ですが、色々な場面で使えると思います。

@echo off

rem utf-8を使いたい場合、コメントを解除して下さい。
rem chcp 65001

rem 変数の遅延評価(!を使う部分)を可能にします。
setlocal ENABLEDELAYEDEXPANSION

rem 変更対象のファイルを抽出するためのパターンです。ワイルドカード形式で指定します。
set pattern="sample ?? document.*"

for %%i in (%pattern%) do (    
    set source=%%i

    rem 変更後のファイル名に使う部分を変更前のファイル名から抽出する変数です。
    rem ~7,2はファイル名の7番目から2文字を切り取るとの意味です。
    rem 例えば、sample 01 document.txtの場合、01になります。
    set filename=!source:~7,2!

    rem 変更後のファイルの拡張子に使う部分を変更前のファイル名から抽出する変数です。
    rem ~1919番目の文字から最後の文字までを切り取るとの意味です。
    rem 例えば、sample 01 document.txtの場合、txtになります。
    set ext=!source:~19!

    rem 変更後のファイル名を指定した変数です。
    rem 例えば、sample 01 document.txtの場合、test 01.txtになります。
    set target=test !filename!.!ext!

    echo !source! is being renamed to !target!...
    rename "!source!" "!target!"
   
    if exist !target! (
        echo [!source!] was renamed to [!target!].
    ) else (
        echo [!source!] could not be renamed to [!target!].
    )
)
endlocal

ビルドツールGradleをEclipseで使うための手順

ビルドツールGradleをEclipseで使うための手順です。
Gradleは、AntやMavenのようにビルドを自動化するツールですが、
AntやMavenよりシンプルで便利な感じです。

Eclipse 4.4(Luna) WTP 英語版を立ち上げたことを前提にして説明します。
日本語版でも問題ないと思います。

  1. Help > Eclipse Marketplace > Searchで、FindフィールドにGradleを入力後、エンターキーを押します。
  2. 名前にGradleが入ったプラグインの一覧が出ますが、Gradle IDE PackでInstallボタンを押します。
  3. Confirm Selected Featuresで、Gradle IDE Packにチェックを入れて、Confirmボタンを押します。
  4. Review Licensesで、I accept ...にチェックを入れて、Finishボタンを押します。これでGradleのEclipseプラグインのインストールが開始されます。
  5. インストール途中、Security Warningが出ますが、OKを押して下さい。
  6. You will need to restart Eclipse...と出ますが、Yesを押して下さい。

これでインストール完了です。


簡単なGradleプロジェクトを作ってみましょう。
これはやらなくてもいいです。やりたい方だけ、やってみて下さい。

  1. EclipseのPackage Explorer上で、Ctrl+Nキーを押して、Gradle Projectを選びます。
  2. Project nameに欲しいプロジェクト名を書いて、Sample projectセレクトボックスからJava Quickstartを選んで、Finishボタンを押します。ここでは仮にgtestとします。適当に読み替えて下さい。
  3. すると、Package Explorer上にgtestプロジェクトが生成されます。

(最初は必要なファイルをリモートから取得するせいか? ちょっと時間がかかるようです。)

  1. Window > Show View > Other > Gradle Tasksを選んで、OKボタンを押します。
  2. Gradle Tasksビューで、Projectセレクトボックスからgtestを選び、その下のリストからbuildをダブルクリックします。
  3. これでビルドが始まります。ビルドの過程はConsoleビューで確認できます。
  4. 最後にConsoleビューにBUILD SUCCESSと表示されるはずです。ビルド成功です。おめでとうございます。

f:id:jeongman7:20140628145807j:plain

これでGradleを使用する準備ができました。

後はGradleのビルドスクリプト(build.gradle)を作成する方法を学習すればいいですね。
といっても、これが一番時間かかりそうです。
でも、ビルドスクリプトに使う言語がGroovyなので、何でもできそうだし、
XMLを使うMavenより簡潔なので、比較的楽そうです。

Erlang 基礎ポイント6 - 並行処理3

今日はErlangの並行処理で、モニタについて勉強します。

モニタとは

あるErlangプロセスを監視したいが、リンクを作ると、お互いのErlangプロセスが
密接に関連するようになるので、気をつけなければなりません。
監視のことでプログアムに障害が起きたり、構造が複雑になっては本末転倒ですね。

こういう時、モニタを使います。
監視したいErlangプロセスと直接付き合わなくても(リンクしなくても)、
監視できるようにするものがモニタなのです。

次のような関数を呼び出して現在のErlangプロセスをモニタにできます。

monitor(process, 監視対象のErlangプロセスのPID)

リンクは双方向

リンクの場合、どちらかのErlangプロセスが死んだら、死んでいない方のErlangプロセスに
必ず、そのシグナルが送られます。
つまり、双方向の特性を持っています。
以下のサンプルソースを見て下さい。

% monitor_test1.erl
-module(monitor_test1).
-export([monitor_test1/0]).

monitor_test1() ->
  PPid = spawn(fun p/0),
  register(parent, PPid).

p() ->
  CPid = spawn_link(fun c/0), % Erlangプロセス間でリンクを作成
  register(child, CPid),
  loop("Parent").

c() ->
  loop("Child").

loop(Msg) ->
  receive
    exit ->
      exit;
    error ->
      1 / 0;
    Any ->
      io:format("Any ~p ~n", [Any]),
      loop(Msg)
  after 2000 ->
    io:format("Process ~p ~n", [Msg]),
    loop(Msg)
  end.

% REPL環境で試したものです。
1> c(monitor_test1).
./monitor_test1.erl:22: Warning: this expression will fail with a 'badarith' exception 
{ok,monitor_test1}
2> monitor_test1:monitor_test1().
true
Process "Child" 
Process "Parent" 
Process "Parent" 
Process "Child" 
3> parent ! error. % 親Erlangプロセスを止めたら、子も止まりました。

=ERROR REPORT==== 24-Jun-2014::12:39:32 ===
Error in process <0.39.0> with exit value: {badarith,[{monitor_test1,loop,1}]}

error
4> monitor_test1:monitor_test1().
true
Process "Child" 
Process "Parent" 
Process "Parent" 
Process "Child"  
5> child ! error.% 子Erlangプロセスを止めたら、親も止まりました。
=ERROR REPORT==== 24-Jun-2014::12:39:45 ===
Error in process <0.44.0> with exit value: {badarith,[{monitor_test1,loop,1}]}

error

親と子のどちらかが終了すると、必ず片方も終了するのが分かります。
spawn_linkでErlangプロセス間でリンクを作ったから、当然な結果ですね。

しかし、監視だけのためにリンクを作ると、親と子の間を制御するのに面倒そうですね。
モニタの方を見てみましょう。

モニタは一方向

それに比べると、モニタの場合は一方向です。
モニタになったErlangプロセスは監視対象のErlangプロセスが死んだら、そのシグナルを補足できますが、
逆にモニタのErlangプロセスが死んでも監視対象のErlangプロセスには影響が及びません。

サンプルソースを見てみましょう。
monitor_test2関数が二つあることに注意して下さい。

% monitor_test2.erl
-module(monitor_test2).
-export([monitor_test2/0, monitor_test2/1]).

monitor_test2() -> monitor_test2(fun(CPid) -> CPid end).

monitor_test2(F) ->
  PPid = spawn(fun() -> p(F) end),
  register(parent, PPid).

p(F) ->
  CPid = spawn(fun c/0), % 親と子の間は何の関連もない。
  register(child, CPid),
  F(CPid),
  loop("Parent").

c() ->
  loop("Child").

loop(Msg) ->
  receive
    exit ->
      exit;
    error ->
      1 / 0;
    Any ->
      io:format("Any ~p ~n", [Any]),
      loop(Msg)
  after 2000 ->
    io:format("Process ~p ~n", [Msg]),
    loop(Msg)
  end.

% REPL環境で試したものです。
1> c(monitor_test2).
./monitor_test2.erl:25: Warning: this expression will fail with a 'badarith' exception 
{ok,monitor_test2}
2> monitor_test2:monitor_test2().
true
Process "Child" 
Process "Parent" 
Process "Parent" 
Process "Child" 
3> child ! error. % 子Erlangプロセスを止めても、

=ERROR REPORT==== 24-Jun-2014::13:19:23 ===
Error in process <0.40.0> with exit value: {badarith,[{monitor_test2,loop,1}]}

error
Process "Parent"  % 親Erlangプロセスは生きています。
Process "Parent" 
Process "Parent"  
4> parent ! error. % 親Erlangプロセスを止めたら、やっと、全部止まりました。

=ERROR REPORT==== 24-Jun-2014::13:19:28 ===
Error in process <0.39.0> with exit value: {badarith,[{monitor_test2,loop,1}]}

error

Erlangプロセス生成時、spawn_linkでなく、spawnを使ったから、
リンクは作られず、終了シグナルの伝播は起こりません。
しかし、以下のように親Erlangプロセスをモニタにすると、

5> monitor_test2:monitor_test2(fun(CPid) -> monitor(process, CPid) end). % 子Erlangプロセス(CPid)を監視
true
Process "Child" 
Process "Parent" 
Process "Parent" 
Process "Child" 
6> child ! error.  % 子Erlangプロセスを止めたら、

=ERROR REPORT==== 24-Jun-2014::13:21:13 ===
Error in process <0.45.0> with exit value: {badarith,[{monitor_test2,loop,1}]}

% 親Erlangプロセスでそれを補足できました!
Any {'DOWN',#Ref<0.0.0.86>,process,<0.45.0>,   
            {badarith,[{monitor_test2,loop,1}]}} 
error
Process "Parent"  % 親プロセスは動きつづけています。
Process "Parent" 
7> parent ! exit.
exit

上記のように監視対象(子Erlangプロセス)を監視できて、モニタとなるErlangプロセスは
生きていることが分かります。

逆に以下のようにモニタとなるErlangプロセスが終了しても、

8> monitor_test2:monitor_test2(fun(CPid) -> monitor(process, CPid) end).
true
Process "Child" 
Process "Parent" 
Process "Parent" 
Process "Child" 
Process "Child" 
Process "Parent" 
Process "Parent" 
Process "Child"  
9> parent ! error. % モニタのErlangプロセスを止めても、

=ERROR REPORT==== 24-Jun-2014::13:23:30 ===
Error in process <0.49.0> with exit value: {badarith,[{monitor_test2,loop,1}]}

error
Process "Child" % 監視対象のErlangプロセスは動きつづける。

監視対象のErlangプロセスには何の影響もありません。
これでモニタはリンクと違って、終了シグナルが一方向にしか送られないことを
確認できました。

システムプロセスとの違い

モニタを使わずにspawn_linkでリンクを作ってシステムプロセスにしてもいいんじゃない?と
疑問を抱くかもしれません。
が、リンクの場合、双方向に終了シグナルが送られるので、監視される側も、監視する側の終了シグナルに
備えるため、システムプロセスにする必要があります。
それに加えてお互いのメッセージを監視しなければなりません。
つまりいうと、監視処理を入れるため、システムプロセスを使うと、プログラムが複雑になるということです。
なので、監視のためなら、その言葉どおり(監視 = monitor)、モニタを使えばいいと思います。

参考

プログラミングErlang

プログラミングErlang

http://www.ymotongpoo.com/works/lyse-ja/ja/14_errors_and_processes.html#id4
http://erlangworld.web.fc2.com/concurrent_programming/monitor.html

今日はここまです。
次回は分散システムについて勉強したいと思います。

Javascriptでオブジェクトの出身(クラス)を調べる(instanceof演算子)

例えば、次のように配列を生成し、
instanceofに同じウィンドウのArrayクラスを指定すると、
結果はtrueになります。

// ChromeのJavascript Consoleで実行
> var a = [1, 2, 3, 4, 5];
undefined
> a instanceof Array
true

しかし、instanceofに別ウィンドウのArrayクラスに指定すると、
falseになります。

> var a = [1, 2, 3, 4, 5];
undefined
> var w = window.open("");
undefined
> a instanceof w.Array 

たとえ、名前が同じクラス(constructor)のオブジェクトでも、
ウインドウが違ったら、出身が違うオブジェクトになるということですね。

では、クラスの名前(と、できれば中身まで)が同じなら、ウインドウ区別なく
同じ出身にしたい場合、どうすればいいか?

配列オブジェクトの場合、
ECMAScript5をサポートする環境(最新ブラウザ、NodeJSなど)なら、
Array.isArrayという関数を使えばいいです。

次のコードを見ますと、配列aに対して、同じウインドウ上でも、別のウインドウ上でも
isArray関数はtrueを返すことが分かります。

> var a = [1, 2, 3, 4, 5];
undefined
> var w = window.open(""); // 別ウインドウ
undefined
> Array.isArray(a);
true
> w.Array.isArray(a);
true

Array以外のオブジェクトについてはどうすればいいか?
正解は分かりませんが、オブジェクトのconstructor.name属性を調べれば、
大体は対応できると思います。

> var w = window.open("");
undefined
> w.a = [1, 2, 3, 4];
[1, 2, 3, 4]
> w.a.constructor.name === "Array"
true

ただ、文字列ベースの比較なので、中身が偽者の場合、
仕方がありません。
しかし、意図的に偽者を作らない限り、また、セキュリティ(XSS)の脆弱性がない限り、
上記の方法で問題ないと思います。

そもそも、最近、別ウインドウを開くブラウザアプリは、
あまり見なくなったので、いらない内容かも?とも思いましたが、
いざそういうケースに遭うと、面倒なので、この記事を書きました。

この本(Effective Javavscript)が参考になりました。

Effective JavaScript JavaScriptを使うときに知っておきたい68の冴えたやり方

Effective JavaScript JavaScriptを使うときに知っておきたい68の冴えたやり方

Erlang 基礎ポイント5 - 並列処理2

今日はErlangの並行処理における例外処理について勉強したいと思います。

並行処理での例外処理

Erlang 基礎ポイント4で並行処理プログラムの作成が可能になりました。
しかし、現在実行中のErlangプロセス(=Pといいます)から子Erlangプロセス(=Cといいます)を起動し、
そのCからエラーが発生した時の対処方法はまだ勉強していません。
これができないと、Erlangプロセス全体が一つの塊として、機能するのは難しいですね。
まるで細胞同士が協力し、一つの大きい生き物が成り立つのに似ていると思います。

リンク

PからCを生成し、PとCは何の関係もないならば、それまです。
こういう場合、PやCに何かエラーが起こってもお互いは何の関係も影響もありません。

しかし、Cにエラーが起きて、Pも一緒にエラーを起こすか、エラーを修復する処理を
したい場合、PとCの間にリンクを作らなければなりません。
リンクは言葉通りにPとCが繋がっているのを意味し、
一方のErlangプロセスに何か起きた場合、
他方のプロセスにそれが伝わり、他方が自分も終了させたり他の処理をしたい時、使われます。

普通のErlangプロセスを生成する時は、spawn()関数を使いますが、
リンク付きのErlangプロセスを生成したい場合、spawn_link()関数を使います。
以下のサンプルでspawn()とspawn_link()を比較してみましょう。
生成した子Erlangプロセスでエラーが起きた時の動作の違いが分かります。

まず、spawnを使うサンプルです。

% spawn_sub.erlファイルです。
% spawn_test.erlとspawn_link_test.erlで共通で使うための関数を持っています。
-module(spawn_sub).
-export([loop/1]).

% 2秒おきにメッセージ出力
loop(Msg) ->
  receive
    % 終了処理
    exit ->
      exit;
    % わざと、0で割ってエラーを起こす。
    % コンパイル時、警告が出ますが、学習用なので、無視して下さい。
    error ->
      1 / 0
  after 2000 ->
    io:format("Process ~p ~n", [Msg]),
    loop(Msg)
  end.

% spawn_test.erlファイルです。
-module(spawn_test).
-import(spawn_sub, [loop/1]).
-export([spawn_test/0]).

% テストメソッド
spawn_test() ->
  spawn(fun p/0).

% 親Erlangプロセス
p() ->
  CPid = spawn(fun c/0),
  % 後でchildと言う名前で子Erlangプロセスにerrorメッセージを送れるようになる。
  register(child, CPid),
  loop("Parent").

% 子Erlangプロセス
c() ->
  loop("Child").

% REPL環境で試したものです。
1> c(spawn_sub).
./spawn_sub.erl:15: Warning: this expression will fail with a 'badarith' exception
{ok,spawn_sub}
2> c(spawn_test). 
{ok,spawn_test}
3> spawn_test:spawn_test().
<0.44.0>
Process "Child" 
Process "Parent" 
Process "Parent" 
Process "Child" 
Process "Child" 
Process "Parent" 
4> child ! error.% 子Erlangプロセスにエラーを起こします。

=ERROR REPORT==== 24-Jun-2014::10:12:28 ===
Error in process <0.45.0> with exit value: {badarith,[{spawn_sub,loop,1}]}

error
Process "Parent" % Childが出力されなくなりました。しかし、Parentは出力し続けています。
Process "Parent" 
Process "Parent" 

次はspawn_linkを使うサンプルです。

% spawn_link_test.erlファイルです。
-module(spawn_link_test).
-import(spawn_sub, [loop/1]).
-export([spawn_link_test/0]).

spawn_link_test() ->
  spawn(fun p/0).

p() ->
  CPid = spawn_link(fun c/0),  % ここだけがspawn_testと違います。
  register(child, CPid),
  loop("Parent").

c() ->
  loop("Child").

% REPL環境で試したものです。
1> c(spawn_sub).
./spawn_sub.erl:15: Warning: this expression will fail with a 'badarith' exception
{ok,spawn_sub}
2> c(spawn_link_test).
{ok,spawn_link_test}
3> spawn_link_test:spawn_link_test().
<0.44.0>
Process "Child" 
Process "Parent" 
Process "Parent" 
Process "Child" 
Process "Child"  
Process "Parent" 
Process "Parent" 
Process "Child"  
4> child ! error. % 子Erlangプロセスにエラーを起こします。

=ERROR REPORT==== 28-May-2014::13:41:59 === % 親Erlangプロセスも子Erlangプロセスも終了してしまいました。
Error in process <0.45.0> with exit value: {badarith,[{spawn_sub,loop,1}]} 

error
5> 

spawnの方はCが死んでも、Pは動き続けていることが分かります。
つまり、CがどうなってもPは構いません。
なんというか、単細胞がただ分裂するような感じですね。
Javaでたとえると、単純にスレッドを起こしてそれでおしまいという感じです。

逆にspawn_linkの方はCが死んだら、Pも死ぬことが分かります。
プロセスが全部死ぬので、実際のシステムだったら、システムの停止を意味するので、まずいでしょうが、
spawnの代わりにspawn_linkを使うだけで、複数Erlangプロセスがひとかたまりとして、
動く(... 死ぬ)ようになる点はすごいと思います。
Javaでこれをやるためには親スレッドと子スレッドが通信するための何らかの
仕組み(フラグ変数、ブロッキングキュー、Latch等)が必要になってくるでしょう。

システムプロセス

spawn_linkを使って、PとCの間にリンクが置けることが分かりました。
しかし、それだけでは、Cにエラーが起きて死んだ場合、Pも死ぬだけです。
Cが死んで、Pも死んで、PのPも死んで... 結果的にシステムが停止...
これでは実用性がないですし、耐久性があるシステムとはいえないですね。
システムのどこかにエラーが起きた場合、それを捕らえて、修復したり、一部の機能を停止したりして、
システムが動き続けるようにするのが理想的でしょう。

Cにエラーが起きて、Cが死んだ場合、Pも一緒に死ぬのではなく、
PがCのエラーを捕らえて何かをしたい場合、Pをシステムプロセスに指定する必要があります。
Pをシステムプロセスにすると、Cのエラーを捕らえて何かをすることができます。
以下のサンプルを見てみましょう。

% system_process_test.erlファイルです。
-module(system_process_test).
-export([test/0]).

test() ->
  spawn(fun p/0).

p() ->
  % システムプロセスに変身します。
  process_flag(trap_exit, true),
  CPid = spawn_link(fun c/0),
  register(child, CPid),
  loop("Parent").

c() ->
  loop("Child").

loop(Msg) ->
  receive
    {'EXIT', Pid, Reason} ->
      io:format("We catched error(Pid:~p, Reason:~p)~n", [Pid, Reason]),
      loop(Msg);
    exit ->
      exit;
    error ->
      1 / 0 
  after 2000 ->
    io:format("Process ~p ~n", [Msg]),
    loop(Msg)
  end.

% REPL環境で試したものです。
2> c(system_process_test).
./system_process_test.erl:25: Warning: this expression will fail with a 'badarith' exception
{ok,system_process_test}
3> system_process_test:test().
<0.43.0>
Process "Child" 
Process "Parent" 
Process "Parent"Process "Child" 
Process "Child" 
Process "Parent" 
4> child ! error. % 子Erlangプロセスにエラーを起こします。

=ERROR REPORT==== 28-May-2014::13:55:31 ===  % エラーは発生しますが...
Error in process <0.44.0> with exit value: {badarith,[{system_process_test,loop,1}]}

We catched error(Pid:<0.44.0>, Reason:{badarith,  % PでCのエラーをちゃんと捕らえています。
                                       [{system_process_test,loop,1}]})
error
Process "Parent"  % エラーを捕らえた後もPは動きつづけています。
Process "Parent" 
Process "Parent" 
Process "Parent" 

システムプロセスにしたい場合、process_flag(trap_exit, true)を呼び出せばいいです。
trueの代わりにfalseにすると、普通のErlangプロセスになりますが、
falseに指定することはあまりないと思います。
そもそもprocess_flag自体を呼び出さなければ、普通のErlangプロセスになるからです。

明示的なリンクの作成

spawn_linkでなく、spawnでErlangプロセスを作成しましたが、
後で親Erlangプロセスと子Erlangプロセスの間に
リンクを作成したい場合、link関数を使えばいいです。
以下のサンプルを参考して下さい。

% link_test.erlファイルです。
-module(link_test).
-import(spawn_sub, [loop/1]).
-export([test/0]).

test() ->
  spawn(fun p/0).

p() ->
  CPid = spawn(fun c/0),
  link(CPid), % 明示的にリンクを作成します。
  register(child, CPid),
  loop("Parent").

c() ->
  loop("Child").

% REPL環境で試したものです。
1> c(link_test).
{ok,link_test}
2> c(spawn_sub).
./spawn_sub.erl:15: Warning: this expression will fail with a 'badarith' exception
{ok,spawn_sub}
3> link_test:test().
<0.44.0>
Process "Child" 
Process "Parent" 
Process "Parent" 
Process "Child" 
Process "Child" 
Process "Parent" 
Process "Parent" 
Process "Child" 
4> child ! error. % 子Erlangプロセスにエラーを起こします。

=ERROR REPORT==== 28-May-2014::14:01:20 === % 親Erlangプロセスも子Erlangプロセスも止まりました。
Error in process <0.45.0> with exit value: {badarith,[{spawn_sub,loop,1}]}

error
5> 

注意すべきは一見、spawn_linkはspawn + linkで代替できそうですが、実はそうではありません。
spawn_linkはErlangプロセスの生成とリンクの作成が原子的に実行されますが、
spawn + linkはErlangプロセスの生成(spawn)とリンクの作成(link)の間に隙があるので、
原子的に動くのではないからです。
spawnでErlangプロセスを生成した後、linkが実行される前のほんの少しの間に
生成したErlangプロセスが死ぬことがありえます。

今日はここまでにします。
ここまで勉強すると、ある程度、Erlangで並行処理プログラムを作れるのではないでしょうか。
Erlangは並行処理プログラムの作成がとても簡単ですね。
次は並行処理の続きでモニタについて勉強したいと思います。

参考書

プログラミングErlang

プログラミングErlang

Erlang 基礎ポイント4 - 並行処理1

今日はErlangで並行処理プログラムを作成する方法を勉強します。

並行処理

並行処理とは、一つのプログラムの中で複数の処理を同時に実行させることです。
WindowsLinuxなどのOSでは、複数のプログラムが複数のプロセスとして同時に実行されます。
Apache TomcatなどのAPサーバの内部では、複数の処理単位(リクエスト、サーバ)が複数のスレッドとして同時に実行されます。

Erlangでは、複数の処理が複数のプロセスとして実行されます。
しかし、名前はプロセスですが、ErlangのプロセスはOSのプロセスとは違います
あくまでErlang内に存在するプロセスなのです。
これからErlangの並行処理に登場するプロセスは、Erlangプロセスと意識するようにしましょう。

基本

Erlangで作成する並行処理プログラムはJavaなどの言語で記述するスレッド基盤のプログラムとは記述方法が違います。
が、プログラム(又はメインスレッド・メインプロセス)の実行中にいきなり並行に何か(Erlangプロセス)を起動するということは同じです。

Erlangで並行処理プログラムを書くのはとても簡単ですが、それでも掘り下げると色々出てきます。
ここでは最小限の説明だけをします。後はマニュアルや他のウェブサイトを参考して、知識を拡張していけばいいと思います。

まずは、サンプルプログラムを分析し、その後、並行処理プログラムの作成方法を整理してみましょう。

% concurrent.erl
-module(concurrent).
-export([start/0]).
start() -> 
  % A
  spawn(fun() -> echo(1) end).

echo(NoMsgCnt) ->
  % B
  receive
    shutdown ->
      io:format("Shutdown echo~n");
    {one, Msg} ->
      io:format("One Msg ~p~n", [Msg]),
      echo(NoMsgCnt);
    {two, Msg} ->
      io:format("Two Msg ~p~n", [Msg]),
      echo(NoMsgCnt);
    Unknown ->
      io:format("Unknown Msg ~p~n", [Unknown]),
      echo(NoMsgCnt)
  after
  % C
    7000 ->
      io:format("No Msg ~p~n",[NoMsgCnt]),
      echo(NoMsgCnt + 1)
  end.

まず、start関数内のコメントAを見てみましょう。
spawn関数が見えますね。
spawn関数を呼び出すと、引数に指定した関数が処理内容として新しいErlangプロセスが生成及び実行されます
新しいErlangプロセスが生成されると、spawn関数を実行したErlangプロセスと新しいErlangプロセスが同時、つまり、並行で
実行されるようになります。
spawn関数はErlangプロセスのID(PID)を返します。PIDは後ほど、Erlangプロセスと通信する時に必要です。

spawn関数に指定した関数の内部ではecho関数を呼び出しています。
echo関数の内部を見てみましょう。新しいErlangの文法が出てきました。
receiveafterですね。
基本的にspawn関数に指定する関数の内容は何でもいいです。
非同期で何かの処理を分散して行うように、新しいErlangプロセスを実行した後、そのErlangプロセスを忘れてもいい場面もあるでしょうが、
自分を実行した親Erlangプロセスはもちろんのこと、他のErlangプロセスと通信する必要がある場面がもっと多いと思います。

プロセス間の通信のため、Javaでしたら、同期しながら、変数を値を調べるとか...色々と頭が痛くなりますが、
(concurrentパッケージにあるクラスを使えば大体解決できますが、Erlangよりは確かに冗長です)
Erlangでは非常に単純にErlangプロセス間の通信が実現できます。

新しいErlangプロセスが立ち上がると、そのErlangプロセスのためのメールボックスが用意されます。
形こそ、hotmailgmailのようなメールとは違いますが、概念的には同じです。
receive文が実行されると、メールボックスにメッセージが入ってくるまで待機(ブロッキング)状態になります。
メッセージが同時に多数届いた場合、それぞれのメッセージが非同期で処理され、処理が終わったメッセージは削除されます。
メッセージはreceive内のパターンと照合され、マッチしたパターンの処理内容が実行されます。
もし、どのパターンにもマッチしなかった場合、何も実行されません。

afterには一定の時間がすぎるまでメッセージが届いていない場合、つまり、タイムアウトした場合、
処理される内容が置かれます。単位はmsです。
上記には7000とあるので、7秒間、メッセージが届かない場合、No Msg Xと表示されます。

receive内の各パターン及びafterの処理内容を見ると、処理内容の最後に再帰呼出(echo関数の呼出)をしているのが分かります。
これはまた次のメッセージを待つためです。再帰呼出をしないと、そのままErlangプロセスが終了してしまいますので、ご注意下さい。
shutdownパターンには再帰呼出がないことから、Erlangプロセスを終了するということが分かります。
パターンにあるアトムがshutdownですから、意図的ですね。

これでErlangで並行処理を記述することができました。

次は並行処理を実行し、Erlangプロセスと通信する方法を勉強しましょう。
以下は上記のconcurrentモジュールをREPL環境でテストしたものです。

1> c(concurrent).
{ok,concurrent}
2> P = concurrent:start().
<0.39.0>
No Msg 1          
3> P ! {one, "Hello"}.
One Msg "Hello"
{one,"Hello"}
4> P ! {two, "Hello"}.
Two Msg "Hello"
{two,"Hello"}
5> P ! {three, "Hello"}.
Unknown Msg {three,"Hello"}
{three,"Hello"}
No Msg 2                
No Msg 3        
6> P ! shutdown.
Shutdown echo
shutdown
7> 

concurrentモジュールをコンパイルし、start関数を呼び出しました。
戻り値のPIDを変数Pに代入しました。
PはPIDですが、Erlangプロセスへの電話だと考えればいいです。
Pへの連絡はどうすればいいか? ビックリマーク(!)を使えばいいです。

PID ! メッセージ

です。これがErlangプロセスにメッセージを送る方法です。簡単でしょう?
これでメッセージが該当PIDのErlangプロセスに送られると、
メッセージとErlangプロセスのreceive内にあるパターンとの照合が行われ、
マッチしたパターンの処理内容が実行されます。

整理してみましょう。

% 新しいErlangプロセスの生成は
PID = spawn(fun() -> xxx(引数...) end)

% Erlangプロセスでメッセージを受け取るには
xxx(引数...) ->
  receive
    パターン1 ->
      処理1,
      ...
      処理x,
      xxx(引数...); % xxx()を再帰呼出しないと、終了してしまう。
    ...
    パターンx ->
      処理1,
      ...
      処理x,
      xxx(引数...) % 最後のパターンにはセミコロンを書かない
  after % afterは省略可能
    タイムアウト値(ms) ->
      処理
     xxx(引数...)
  end.

% Erlangプロセスと通信するためには
PID ! メッセージ 

さらに

これで、Erlangで動く並行処理プログラムを書けるようになりましたが、
ちょっと足りない部分があります。
それぞれのErlangプロセスとやり取りするため、メッセージを送る時にspawn関数から返されるPidが必要ですが、
Pidをいちいち覚えておくのは面倒です。
そこで、ErlangにはPidを意味ある名前で登録できるようにする関数が用意されています。
その関数でPidを名前に登録しておき、後でその名前でErlangプロセスにメッセージを送ればいいです。

使い方は簡単です。以下のようにすればいいです。

register(名前を表すアトム, Pid)

反対に登録した名前を解除する関数もあります。

unregister(名前を表すアトム)

簡単ですね。

以下は基本で示したサンプルプログラムをregisterを使うように修正し、REPL環境で試したものです。

% concurrent2.erl
-module(concurrent2).
-export([start/0]).
start() -> 
  P = spawn(fun() -> echo(1) end),
  register(test_echo, P).

echo(NoMsgCnt) ->
  receive
    shutdown ->
      io:format("Shutdown echo~n");
    {one, Msg} ->
      io:format("One Msg ~p~n", [Msg]),
      echo(NoMsgCnt);
    {two, Msg} ->
      io:format("Two Msg ~p~n", [Msg]),
      echo(NoMsgCnt);
    Unknown ->
      io:format("Unknown Msg ~p~n", [Unknown]),
      echo(NoMsgCnt)
  after
    7000 ->
      io:format("No Msg ~p~n",[NoMsgCnt]),
      echo(NoMsgCnt + 1)
  end.

% REPL環境でテスト。
1> c(concurrent2).
{ok,concurrent2}
2> concurrent2:start().
true
No Msg 1        
No Msg 2        
No Msg 3                
3> test_echo ! {one, "Hello"}.
One Msg "Hello"
{one,"Hello"}
4> test_echo ! {two, "Hello"}.
Two Msg "Hello"
{two,"Hello"}
5> test_echo ! {three, "Hello"}.
Unknown Msg {three,"Hello"}
{three,"Hello"}
No Msg 4                        
6> test_echo ! shutdown.
Shutdown echo
shutdown
7> 

PIDでなく、test_echoという分かりやすい名前でErlangプロセスと通信できていますね。

Erlangプロセスは他の言語のスレッドやOSのプロセスより早くて効率がいいです。
数万・数十万個のプロセスも問題なく処理できるそうだから、すごいですね。
自分でErlangの威力が直接感じられるプログラムが書けたらいいですね。
イデアが浮かんだら、ぜひやってみたいと思います。
ちなみにErlangでネットワークプログラムを作ると、NodeJSと比べてどうでしょうか。
メリットが少し似ているようが気がします。ググれば出るのかな...

今日はここまでにします。
元々今日でErlang 基礎ポイントを最後にしようと思いましたが、
並行処理での例外処理について意外と書くことが多くて、
次の記事に続けて書くことにします。
最後になるかどうかは分かりません。これはあくまで個人的な理由で書くものですから...笑