前回記事において、「シングルスレッドとノンブロッキングI/Oという特徴をJavaScript自体が持っている」と記載しましたが、間違いです。正しくは、「マルチスレッドとI/O全般をJavaScriptが持っていない」です。
JavaScriptの言語仕様は、ECMAScriptとして策定されています。ECMAScriptには、スレッドやI/Oに関しては記述されていません。しかし、サーバサイドJavaScriptを作成するうえで、I/O機能は必須であるため、各サーバサイドJavaScriptの実装(RingoJSの前身であるHelmaや、Aptana Jaxerなど)が独自に拡張していました。
各自が独自に実装していると、API互換性などで問題が出てくるため、「CommonJS」が策定されました。このCommonJSは、サーバサイドJavaScriptだけの仕様ではなく、さまざまなアプリケーションをJavaScriptで作成するための仕様です。
前回記事では、「Node.jsは、このCommonJSにのっとって開発されています」と記載しましたが、現実的には、CommonJSのごく一部(moduleとassert)を実装しているだけです。Node.jsの特徴である「シングルスレッドでのノンブロッキングI/O」は汎用的な機能ではないためか、CommonJSには定義されていません。
今後、Node.jsがCommonJSに沿って、実装されていくかは微妙な所ですが、JavaScriptでアプリケーションを作成する際のガイドラインとして、CommonJSは有用です。
前回記事では、「非同期処理」と「並行処理」を混同している表現がありました。今回は、イベントループのイメージ図とソースコードで説明したいと思います。
Node.jsでは、「並行処理」をシングルスレッドで行うために、「ノンブロッキングI/O」による「非同期処理」を行います。Node.jsでは、イベントループと呼ばれるモデルを採用しています。
イベントループでは、実行する処理をキューにいったん格納し、順に実行していきます。そのため、シングルスレッドでブロックするI/Oを行った場合、【3】のイベント完了後に【4】のイベントを実行されます。
【3】と【4】を並行処理させるために、ノンブロッキングI/Oを使い、【3】のイベントの完了を待たずに【4】のイベントハンドラを実行させます。【3】のイベントハンドラ完了結果は、コールバック関数を登録することにより、受け取ります。もし、【3】のイベントハンドラ実行が処理をブロックしてしまった場合、【4】のイベントは【3】のブロックが解除されるのを待たされます。
Node.jsでは、イベントを生成して処理を制御します。例えば、HTTPサーバでクライアントからリクエストを受け取ったときや、ファイルの読み込みが完了したときなどにイベントが、生成されます。
次に、実際のコードを例にして説明します。この例は、「index.html」を読み込んで返すだけのWebアプリケーションです。
まず、「index.html」を作成してください。
<html> <head> <meta charset="UTF-8"> <title>index.html</title> </head> <body> index.html </body> </html>
次に、以下のコードを「server.js」という名前で保存してください。
// httpとfsモジュールを読み込む var http = require('http'), fs = require('fs'); // httpサーバの作成 http.createServer(function(req, res) { console.log("start"); // index.htmlを読み込んで表示 fs.readFile('index.html', function(err, content) { if (err) { throw err; } console.log("response end"); res.writeHead(200, {'Content-Type':'text/html; charset=utf-8'}); res.end(content); }); console.log("end"); // index.htmlの読み込みを待たずにレスポンスを返す //res.writeHead(200, {'Content-Type':'text/html; charset=utf-8'}); //res.end(); }).listen(8192, '127.0.0.1'); console.log('http://127.0.0.1:8192/');
HTTPのリクエストとレスポンスを示す2つの変数「req」「res」を受け取る関数(イベントハンドラ)をhttp.createServer()に渡して、HTTPサーバを作成します。HTTPリクエスト(イベント)を受け取ると、この関数(イベントハンドラ)をイベントループに登録します。
Node.jsは、イベントキューからイベントを取り出して実行します。Node.jsコンソールから実行します。
$ node server.js http://127.0.0.1:8192/
Webブラウザで、「http://127.0.0.1:8192/」を開きます。
index.htmlが表示されます。Node.jsを実行したコンソールには、ログが表示されます。
$ node server.js http://127.0.0.1:8192/ start end response end
index.htmlを読み込む関数である「fs.readFile()」に対して、コールバック関数を渡しています。「fs.readFile()」は、Node.jsにより、非同期で実行され、ファイルの読み込みが完了した時点で、コールバック関数を呼び出すためです。そのため、ログの順番は、「start」「end」「response end」の順になります。
次ページでは、より深くイベントループを理解するために、ある実験をしてみましょう。
Copyright © ITmedia, Inc. All Rights Reserved.