ビルドツールGradleをEclipseで使うための手順
ビルドツールGradleをEclipseで使うための手順です。
Gradleは、AntやMavenのようにビルドを自動化するツールですが、
AntやMavenよりシンプルで便利な感じです。
Eclipse 4.4(Luna) WTP 英語版を立ち上げたことを前提にして説明します。
日本語版でも問題ないと思います。
- Help > Eclipse Marketplace > Searchで、FindフィールドにGradleを入力後、エンターキーを押します。
- 名前にGradleが入ったプラグインの一覧が出ますが、Gradle IDE PackでInstallボタンを押します。
- Confirm Selected Featuresで、Gradle IDE Packにチェックを入れて、Confirmボタンを押します。
- Review Licensesで、I accept ...にチェックを入れて、Finishボタンを押します。これでGradleのEclipseプラグインのインストールが開始されます。
- インストール途中、Security Warningが出ますが、OKを押して下さい。
- You will need to restart Eclipse...と出ますが、Yesを押して下さい。
これでインストール完了です。
簡単なGradleプロジェクトを作ってみましょう。
これはやらなくてもいいです。やりたい方だけ、やってみて下さい。
- EclipseのPackage Explorer上で、Ctrl+Nキーを押して、Gradle Projectを選びます。
- Project nameに欲しいプロジェクト名を書いて、Sample projectセレクトボックスからJava Quickstartを選んで、Finishボタンを押します。ここでは仮にgtestとします。適当に読み替えて下さい。
- すると、Package Explorer上にgtestプロジェクトが生成されます。
(最初は必要なファイルをリモートから取得するせいか? ちょっと時間がかかるようです。)
- Window > Show View > Other > Gradle Tasksを選んで、OKボタンを押します。
- Gradle Tasksビューで、Projectセレクトボックスからgtestを選び、その下のリストからbuildをダブルクリックします。
- これでビルドが始まります。ビルドの過程はConsoleビューで確認できます。
- 最後にConsoleビューにBUILD SUCCESSと表示されるはずです。ビルド成功です。おめでとうございます。
これで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)、モニタを使えばいいと思います。
参考
- 作者: Joe Armstrong,榊原一矢
- 出版社/メーカー: オーム社
- 発売日: 2008/02/23
- メディア: 単行本(ソフトカバー)
- 購入: 8人 クリック: 284回
- この商品を含むブログ (97件) を見る
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の冴えたやり方
- 作者: David Herman,吉川邦夫
- 出版社/メーカー: 翔泳社
- 発売日: 2013/02/19
- メディア: 大型本
- 購入: 1人 クリック: 109回
- この商品を含むブログを見る
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は並行処理プログラムの作成がとても簡単ですね。
次は並行処理の続きでモニタについて勉強したいと思います。
参考書
- 作者: Joe Armstrong,榊原一矢
- 出版社/メーカー: オーム社
- 発売日: 2008/02/23
- メディア: 単行本(ソフトカバー)
- 購入: 8人 クリック: 284回
- この商品を含むブログ (97件) を見る
Erlang 基礎ポイント4 - 並行処理1
今日はErlangで並行処理プログラムを作成する方法を勉強します。
並行処理
並行処理とは、一つのプログラムの中で複数の処理を同時に実行させることです。
Windows、Linuxなどの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の文法が出てきました。
receiveとafterですね。
基本的にspawn関数に指定する関数の内容は何でもいいです。
非同期で何かの処理を分散して行うように、新しいErlangプロセスを実行した後、そのErlangプロセスを忘れてもいい場面もあるでしょうが、
自分を実行した親Erlangプロセスはもちろんのこと、他のErlangプロセスと通信する必要がある場面がもっと多いと思います。
プロセス間の通信のため、Javaでしたら、同期しながら、変数を値を調べるとか...色々と頭が痛くなりますが、
(concurrentパッケージにあるクラスを使えば大体解決できますが、Erlangよりは確かに冗長です)
Erlangでは非常に単純にErlangプロセス間の通信が実現できます。
新しいErlangプロセスが立ち上がると、そのErlangプロセスのためのメールボックスが用意されます。
形こそ、hotmailやgmailのようなメールとは違いますが、概念的には同じです。
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 基礎ポイントを最後にしようと思いましたが、
並行処理での例外処理について意外と書くことが多くて、
次の記事に続けて書くことにします。
最後になるかどうかは分かりません。これはあくまで個人的な理由で書くものですから...笑
Erlang 基礎ポイント3 - case式, if式 例外
case式
Erlang 基礎ポイント2 - DukeLabでパターン集合について説明しました。
パターン集合の結果から処理を複数の関数に分けることができましたが、
関数の数が無駄に多くなる恐れがありますね。
もちろん、プログラムの処理は可能な限り小さくした方が
モジュール化側面でいいでしょうが、やりすぎると、
かえって複雑なプログラムになりかねないですね。
case式を使うと、一つの関数内で処理部を分けておくことができます。
Javaのswitch caseのようなものですが、判断条件に固定値だけでなく、パターンが使えるので、
もっと強力です。
まず、例をみてみましょう。
前のguard_module.erlがErlang 基礎ポイント2 - DukeLabのパターン集合節で紹介したもので
後のcase_test.erlがcase式を使うように修正したものです。
% guard_module.erl -module(guard_module). -export([guard_test/1]). guard_test(X) when X < 5, X > 1 -> X * X; guard_test(X) when X >= 5; X < 10 -> X - 2; guard_test(X) -> X - 2. % case_test.erl -module(case_test). -export([guard_test/1]). guard_test(X) -> case X of X when X < 5, X > 1 -> X * X; X when X >= 5; X < 10 -> X - 2; X -> X - 2 end.
caseの使い方は次のようです。
他のプログラミングの経験があるなら、問題ないでしょう。
case 評価対象 of パターン1 [when ガード...] -> 結果; ... パターンn [when ガード...] -> 結果 end
最後のパターンにはセミコロンをつけないことに注意して下さい。
評価対象に判断対象(関数引数・変数など)を指定し、
パターンnに判断対象と照合するパターンを書きます。
必要ならガードに追加条件を指定できますが、ガードは省略可能です。
評価対象がパターンに一致すれば、結果が返されます。
一致するパターンがないと、例外が発生するので、注意しましょう。
caseの方が関数の長さが増えましたが、関数の数は減りましたね。
どちらも優先的に使うのではなく、関数の数とcaseの数をバランスよく
配置すればいいと思います。
if式
他のプログラミング言語のif文と同じですが、違いは式なので、戻り値があるということです。
上記のcase式のサンプルをif式に修正したものを載せます。
% if_test.erl -module(if_test). -export([if_test/1]). if_test(X) -> if X < 5, X > 1 -> X * X; X >= 5, X < 10 -> X - 2; true -> X - 3; end. % if_testをREPL環境で試す。 19> c(if_test). {ok,if_test} 20> if_test:if_test(7). 5 21> c(if_test). {ok,if_test} 22> if_test:if_test(3). 9 23> if_test:if_test(6). 4 24> if_test:if_test(-1). -4
if式の文法は他のプログラミング言語のif文と似ていますが、ちょっと違いますね。
まず、複数個の条件を書く時、ifと複数書く必要がありません。
最初にifと書いて、case式のように条件と結果を次々と書いていけばいいです。
それから、最後の条件にtrueと書きましたね。これはelseと考えればいいです。
Erlangのifにはelseがありません。なので、全ての条件に一致しない時の条件を
直接書く必要がありますが、trueと書いておけばOKです。
case 評価対象 -> パターン1 [when ガード...] -> 結果; ... パターンn [when ガード...] -> 結果 end
trueを書かないで、全ての条件に一致しない場合、例外が発生しますので、注意しましょう。
例外
Erlangで例外処理は、ちょっと形式は違いますが、Javaと同じくtry-catch式を使います。
以下の例を見ながら、try-catchを身につけましょう。
% trycatch.erl -module(trycatch). -export([go/1]). go(X) -> try test(X) of Val -> {value, Val} catch throw:exception1 -> io:format("Catch Exception_Test1~n"); throw:exception2 -> io:format("Catch Exception_Test2~n"); exit:_ -> io:format("Catch Exit~n"); error:_ -> io:format("Catch Error~n") after io:format("After~n") end . test(X) -> case X of 1 -> throw(exception1); 2 -> throw(exception2); 3 -> exit(unknown); 4 -> error(unknown) end . % trycatchをREPL環境で試す。 1> c(trycatch). {ok,trycatch} 2> trycatch:go(1). Catch Exception_Test1 After ok 3> trycatch:go(2). Catch Exception_Test2 After ok 4> trycatch:go(3). Catch Exit After ok 5> trycatch:go(4). Catch Error After ok
関数goに1から4までの数字を入れると、例外(throw)2種類、プロセス終了(exit)1種類、エラー(error)1種類を演じてくれます。
throwは呼出元が例外を処理することを期待する時、呼出元に対してエラーをなげる関数です。JavaのChecked Exceptionと似ていますね。
但し、throwでエラーを発生させても、呼出元がtry-catchでエラーを捉えなくても実行はできます。
Javaのようにコンパイルエラーは発生しません。
プログラムで捉えられなかったエラーはErlangが捉えます。その場合、スタックトレースのようなものが表示されるでしょう。
exitはプロセスを終了させたい時、呼出元にエラーをなげる関数です。try-catchでこのエラーを捉えないと、プロセスが終了してしまいます。
errorは呼出元が例外を処理できないような致命的なエラーが発生したことを知らせるため関数です。
Javaでいうと、RuntimeExceptionのようなものでしょうか。
よく見るとErlangのtry-catchはcaseと似ていることが分かります。
tryとcatchの間のコードがまさにcaseのような形式、その後、catchとafterが追加されていますね。
catchはJavaのcatchと同じで、afterはJavaのfinallyのようなものです。
以下はtry-catch式の使い方です。
try 評価対象 of パターン1 [when ガード...] -> 結果; ... パターンn [when ガード...] -> 結果 catch 例外種類1(throw, exit, error): 例外パターン1 [when ガード1] -> 結果1; ... 例外種類n(throw, exit, error): 例外パターンn [when ガードn] -> 結果n after catch結果に関係なく、返す結果 end
Erlangのtry-catchはJavaと違って、式なので戻り値があります。
今日はここまで。
次はErlangでの並行処理について書きます。
[WebSocket] [Java] WebSocketを試す
WebSocketとは
HTML5仕様を構成する要素の一つで一方向通信しかできなかった既存のHTML通信とは違って、
JavascriptのWebSocket APIとサーバサイドのWebSocket機能でクライアントとサーバ間の両方向通信を可能にする技術だ。
WebSocketの特徴
WebSocketは、通信ごとにHTTPコネクションを開く・閉じるのを繰り返すのではなく、
いったんHTTPコネクションを開いたら、コネクションを開いたままにしておいて、データをやりとりする。
コネクションが維持される間は、Ajaxのようにクライアントからサーバへの通信だけでなく、
サーバからクライアントに直接データを送ることもできる。
以下は、ChromeでWebSocketの公式サイトにあるデモを試したキャプチャである。
"Rock it with HTML5 WebSocket"という文字列を何回もSendしているのにコネクションは一つ(?encoding=text)しか発生していない。
通信の内容はFramesタブをクリックすれば、分かる。
WebSocketのAPIについて
サーバAPI
昔はサーブレットコンテナごとに違うWebSocketのAPIがあったが、
今は、JSR 356: JavaTM API for WebSocketという標準ができて、
各ベンダが自分たちのサーブレットコンテナに実装している。
標準APIがあるので、ベンダ依存のAPIは使わないこと。
WebSocketサーバプログラムはWeb(サーブレット・JSP)でもStandalone(mainメソッド)でも作れる。
JavaでWebSocketサーバプログラムを実装するために以下のアノテーションとクラスを使用する。
- @ServerEndPoint
- @OnOpen
- @OnMessage
- @OnClose
- @PathParam
- Encoder
- Decoder
- 他にも色々。
クライアントAPI
WebSocketのクライアントプログラムはどの言語でも作れるが、ここではJavascriptを取り上げる。
Javascriptでは以下のように非常に単純で分かりやすいAPIが存在する。
- WebSocket(url)
- WebSocket.send
- WebSocket.onopen
- WebSocket.onmessage
- WebSocket.onerror
- WebSocket.onclose
サンプル作成
必要なもの
- WebSocketをサポートするサブレットコンテナ(Tomcat8, GlassFish、Jetty等)
- サーバプログラム(Java、JSP)
- クライアントプログラム(Javascript)
サンプル作成スタート
時間を節約するため、サンプルは早く作りたい。
今回はNetBeansを利用してサンプル作ろう。
Netbeansをダウンロード・インストールし、立ち上げて新規プロジェクトを作成する。
カテゴリでJava Web→Webアプリケーションを選択し、次へ、プロジェクト名を入力し、次へ、
サーバーと設定画面で終了ボタンを押すと、プロジェクトが作成される。
さあ、コーディングをしてみよう。
サーバプログラム
送ったメッセージをそのまま返すだけである。
package dukelab.websocket.demo; import javax.websocket.OnMessage; import javax.websocket.server.ServerEndpoint; /** * WebSocketデモ。 * * @author DukeLab */ // Webソケットのサーバ側クラスであること表すアノテーション。 // 引数(wsdemo)はクライアントから接続時、使われるURIを表す。 @ServerEndpoint(value = "/wsdemo") public class WebSocketDemo { @OnMessage public String onMessage(String text) { return text; } }
WebSocketのサーバプログラムは@ServerEndpointアノテーションをつけることで始まる。
@ServerEndpointの引数valueにはクライアントから接続時、使われるURIを指定する。
URIは/で始まらなければならない。
例えば、ウェブアプリケーションがlocalhost:8080で動いていてコンテキストパスがtestなら、
上記のサーバプログラムへのURLはws://localhost:8080/test/wsdemoになる。
ここでは指定していないが、@ServerEndpointアノテーションへの引数に、
メッセージ変換のためのencoder、decoderも指定できる。
本格的なアプリケーションを作る時に、encoderとdecoderが必要になりそうだ。
それから、@OnMessageアノテーションをメッセージを受信するメソッドに指定する。
メソッドの引数textがクライアントから受信したデータということだ。
onMessageメソッド内で色々処理を行って、その応答をreturnすればいい。
@OnMessageアノテーション以外に@OnOpen、@OnCloseもある。
@Onxxxアノテーションがつくメソッド(例 : onMessage(String Text))の引数や応答を返す方法は上記以外にもある。
リクエストが来た時だけでなく、サーバから自発的に送ることもできる。
それらは実装の状況に応じてドキュメントを参照すればいいだろう。
ここでは至極簡単なサンプルサーバプログラムを作った。
上記のクラスだけだ。
web.xmlとかサーブレットは要らない。
本当に便利だ。
クライアントプログラム
サンプルを実行したブラウザはChrome 33.0.1750.117である。
<!DOCTYPE html> <html> <head> <title>WebSocketDemo Client</title> <meta charset="UTF-8"> </head> <body> <script type="text/javascript"> WebSocketDemo = {}; (function(d) { function $(query) { return document.querySelector(query); } function printMessage(msg) { $("#msgbox").innerHTML += "<div>" + msg + "</div>"; } d.connect = function() { var ws = new WebSocket("ws://localhost:8080/WebApplication3/wsdemo"); ws.onmessage = function(event) { printMessage("Server : " + event.data); }; d.webSocket = ws; $("#connect").disabled = true; $("#send").disabled = false; }; d.send = function() { var msg = $("#msg").value; d.webSocket.send(msg); printMessage("Client : " + msg); }; }) (WebSocketDemo); </script> <form action="javascript:void(0);"> <input type="text" id="msg" size="20"> <input type="button" id="connect" value="Connect" onclick="WebSocketDemo.connect();"> <input type="button" id="send" value="Send" onclick="WebSocketDemo.send();" disabled> </form> <div id="msgbox" style="border-style: solid;width: 500px;height: 400px"></div> </body> </html>
Connectボタンを押すと、WebSocketオブジェクトを生成する。
WebSocketのコンストラクタにはスキームがws://又はwss://(SSLの場合)で始まるURLを指定する。
後、サーバからメッセージを受信すると、onmessageハンドラーが呼ばれる。
引数のオブジェクトのdataプロパティからメッセージの取得ができる。
Sendボタンを押すと、WebSocketオブジェクトのsendメソッドを呼び出してメッセージをサーバに送信する。
サーバより実装が簡単だ。
注意点は、IEの場合、WebSocketから使えるのはIE10からだということだ。
IE9まではWebSocketが使えない。
さらに進化したサンプル
上記までは、内部的にやり取りされるのが変わっただけで、既存のAjaxでも同じ機能のアプリケーションは作ることができる。
ここで、サーバから自発的にクライアントにメッセージを送る機能をつけてみよう。いわゆるPUSH型通信。
これはAjaxではできないのだ。
setIntervalで周期的にサーバに見に行くしかないのだ。いわゆるPULL型通信。
Ajax以外にCometというのもあるが、これはコネクション確立後、ただ、Pendingして待つだけだ。
実際の通信が送られるまで待つので、通信量を減らすことはできるが、サーバから応答が返ってくると、
また次のHTTPコネクションを確立しなければならない。
ずっと通信を確立しているWebSocketはAjaxとCometとは歴然と違うのだ。
このさらに進化したサンプルでは、制御用JSPを一個作って、そのJSPで何かを入力すると、
サーバから全てのクライアントにメッセージが送信される機能をつける。
以下は上記のWebSocketDemoクラスに機能を追加したものだ。
package dukelab.websocket.demo; import java.util.Set; import java.util.concurrent.CopyOnWriteArraySet; import javax.websocket.OnClose; import javax.websocket.OnMessage; import javax.websocket.OnOpen; import javax.websocket.Session; import javax.websocket.server.ServerEndpoint; /** * WebSocketデモ。 * * @author dukelab */ @ServerEndpoint(value = "/wsdemo") public class WebSocketDemo { private static Set<Session> ses = new CopyOnWriteArraySet<>(); @OnOpen public void onOpen(Session session) { System.out.println("onOpen : " + session); ses.add(session); } @OnMessage public String onMessage(String text) { return "echo => " + text; } @OnClose public void onClose(Session session) { System.out.println("onClose : " + session); ses.remove(session); } public static void sendMessage(String msg) { for (Session ses : ses) { ses.getAsyncRemote().sendText(msg); } } }
要は、接続時(onOpen)にセッションをスレッドセーフなコレクションに入れておいて、
そのセッションに対して操作をすればいい。
切断時(onClose)にセッションをスレッドセーフなコレクションから削除する処理は忘れないように。
<%@page import="dukelab.websocket.demo.WebSocketDemo"%> <%@page contentType="text/html" pageEncoding="utf-8"%> <!DOCTYPE html> <% boolean isPOST = request.getMethod().toLowerCase().equals("post"); if (isPOST) { WebSocketDemo.sendMessage(request.getParameter("msg")); } %> <html> <head> <meta http-equiv="Content-Type" content="text/html; charset=utf-8"> <title>WebSocketDemo Control</title> </head> <body> <%= isPOST ? "Message was sent." : "" %> <form action="" method="post"> <input type="text" name="msg"> <input type="submit" name="submit" value="Send"> </form> </body> </html>
以下の画面で、左が制御用JSPで、右がエコデモである。
制御用JSPでメッセージを入力し、Sendボタンを押すと、全てのクライアントにメッセージが送信される。
これは、なかなか面白い。