こんにちはほそ道です。まずはNode.jsからやって参ります。
とりあえずNode.jsから手を付ける理由
最近、Webページ向けJSライブラリをダウンロードしたり、ちょっと使ってみよっかなみたいな時でも
- 「npm」でインストールせい
- 「Grunt」や「gulp」によってビルドやコンパイルせい
と言われる事が多くなってきたように感じます。
しかし、公式手順に沿っても実行環境やバージョン違いによりエラーになる事も少なくなく
手順に書かれているコマンド内容をある程度理解していないと
対応も手順にそったウッスい感じになり、ハマってしまう事も多いです。。
よってサーバサイドJSのなかでも特にNode.jsはもはや常識としてとらえ、
「Node.jsってどういう仕組みで動くの?」の
基本は押さえておこうと思った次第です。
Node.jsのアプローチ
Node.js(v0.10.26)インストールディレクトリのREADMEを見てみますとwikiへのリンクがありましたので、こちらを拝見してみます。
WikiのIntroductionには下記のように書いてありました。
Node.js is a server-side JavaScript environment that uses an asynchronous event-driven model.
This allows Node.js to get excellent performance based on the architectures of many Internet applications.
適当に訳しますと下記のような感じでしょうか。
Node.jsは 非同期型のイベント駆動モデル を使用したサーバサイドJS環境です。
これはNode.jsが多くのインターネットアプリ構築において、すんばらしいパフォーマンスを得る事を可能にします。
なんか抽象的ですがこいつがポイントのはずなので私なりに補足もしつつ膨らませてみます。
■すんばらしいパフォーマンスを得る為にはサーバリソースを効率的に使う必要がある
■アパッチなどで採用されているマルチスレッドモデルの難点として
・スレッド毎のメモリ領域の圧迫
・ロック制御など処理の複雑化
・I/O待ち
なんかがある
■Node.jsはこれらの問題に対してひとつの答えを出したアプローチを採用している
・Node.jsは書いたコードはひとつのメインスレッドで動作する
->スレッド毎のメモリ領域の圧迫と処理の複雑さの回避
・Node.jsはI/O処理を非同期に実装し、イベントトリガーのコールバックで処理の順序性を保つ
->リソースの効率的使用とI/O待ちの低減
こんなところでしょうか。
すべてのシチュエーションの問題を解決するわけでは無いでしょうが、Node.js的にはこうだぜ、と。
と、いうわけで前置きが長くなりましたがNode.jsが看板的に掲げている非同期型イベント駆動の動きっぷりを見てきたいと思います。
非同期型イベント駆動プログラミング
Node.jsコードを書いてみる
それではファイルを開いて読み込んで表示するだけの簡単なjsを作ってみます。
構成は以下のようにJSファイルとテキストファイルがあるだけです。
.
├── hsmc_fs.js
└── foobar.txt
foo
bar
var fs = require('fs');
fs.readFile("foobar.txt", "utf-8", function(err, data){
if (err) throw err;
console.log("file have just been readable!");
console.log(data);
});
console.log("end");
コードの説明
hsmc_fs.jsを簡単に説明します。
いきなり一行目にrequire
という関数が呼ばれてますがこれはファイルシステム関連のモジュールをロードしてます。モジュールについてはまた今度やっていこうと思いますので今は何となく機能拡張してるんだ程度で。
fs.readFileの第三引数に関数を記載していますが、コレがコールバック関数ですね。
ファイルの読み出しが可能になったというイベントの発生をトリガーにコールバック関数が実行されるというわけです。
メッセージとファイル内のデータを書き出しています。
そしてendという文字列の出力です。
実行結果
それでは実行結果を見てみましょう。
end
file have just been readable!
foo
bar
うわぁ。。「end」が最初に表示されてますね。
これが 非同期的/ノンブロッキング的な動き をしている状態です。
hsmc_fs.jsに記載したコードはメインスレッドで動き出します。
そしてfs.readFile
は非同期型関数として実装されており、
「ファイルを読みに行き、オープンする」
という処理の一部はI/O専門の ワーカスレッドプールから取り出された誰かさん(ワーカスレッド) にお任せしています。
メインスレッドはその間に「end」を出力したというわけです。
すんばらしいパフォーマンスへの結実
で、どこがすんばらしいパフォーマンスに繋がっているのか、というと
ポイントは 「メインスレッドがI/O待ちを行っていない事」 であります。
処理コストの高いI/O処理をワーカに任せている間、
メインスレッドは別の仕事を行う事が出来ます。
先ほどのサンプルで言えば「end」と出力してからコールバックを実行するまでに
別のお仕事依頼があれば、そちらを実行する事も可能だと言う事ですね。
これにより「リソースを効率的に使う事が出来る」
というわけです。
同期型(ブロッキングI/O)プログラミング
コードと実行結果
逆に同期的に処理をさせる事も可能ですので理解を深める為にやってみます。
var fs = require('fs');
data = fs.readFileSync("sample.txt", "utf-8")
console.log("file have just been readable!");
console.log(data);
console.log("end");
readFile
関数をreadFileSync
関数に変えています。
この関数の引数にはコールバックもありません。
さて、実行結果は。。
file have just been readable!
foo
bar
end
ほほう、やはり上から順番に表示されますね。
動作の説明
こちらの処理はワーカーに処理を任せずにメインスレッドが自分でファイルを開いて読んで出力します。
完了イベントを受け取る事も無いのでコールバック関数も無いというわけですね。
こちらの処理はメインスレッドがファイル読み込みをしている間は他の処理はすべてブロックされます。
忙しいアプリケーションを実装する場合、ブロッキングI/Oを多く発生させてしまうと、
Node.jsの思想に反し十分なパフォーマンスが得られなくなってしまいます。
憶測ですが同期型の関数名に「Sync」が付き、
非同期型の関数名に「Async」が付かないのは
やはりNode.js的に標準関数が非同期型にある、という事の意思表示なんでしょうね。
終わりに
また、どのようにして非同期コールしているのか気になったので
ちょっと内部コードを追いかけてみましたが
C++実装処理で非同期コールを行っているようです。
nodeについてはいったんあっさり目に駆け抜けるつもりなのでこの辺で。
今回は以上です。
次回はイベントもといイベントループの仕組みを探ってみます。