js はシングルスレッドの言語です。この特性のため、並行処理を行う際には setTimeout ()、setInterval ()、XMLHttpRequest のようなテクニックが必要です。
ただし、ここでの並行処理は非ブロッキングのみを指します(John Resig の記事How JavaScript Timers Workを参照)。本当のマルチスレッドプログラミングには HTML5 の Web Worker が必要です。
###【Web Worker の使用】
Web Worker の使用は非常に簡単で、スレッド間通信の API は HTML5 の postMessage や Node.js の socket.io メソッドと似ています。
通信:
- 送信側: postMessage (data)
- 受信側: onmessage (event)
Web Worker の終了:
- サブスレッド: self.close ()
- 親スレッド: worker.terminate ()
現在の W3C の仕様によると、Web Worker は専用ワーカー(Dedicated Worker)と共有ワーカー(Shared Worker)の 2 種類に分かれています。
###【専用ワーカー】
Web Worker オブジェクトをインスタンス化し、非同期でサブスレッドファイル worker.js をロードし、その中のコードを実行します。
var worker = new Worker("worker.js");
ワーカーにリスナーを追加します。
worker.onmessage = function (event) {
alert(event.data);
};
worker.js で親スレッドにメッセージを送信します。
postMessage('hello,imweb');
親スレッドのページで送信された情報を確認できます。
また、Web Worker の標準ではオブジェクトパラメーターもサポートされており、JSON データを送信できることを意味します。もう少し複雑な例を見てみましょう。親スレッド:
var worker = new Worker("worker.js");
worker.onmessage = function (event) {
document.getElementById("result").innerHTML=event.data;
};
function start(){
worker.postMessage({'cmd': 'start', 'msg': 'start'});
}
function pause(){
worker.postMessage({'cmd': 'pause', 'msg': 'pause'});
}
function stop(){
worker.postMessage({'cmd': 'stop', 'msg': 'stop'});
}
function msg(){
worker.postMessage({'msg': 'hello imweb'});
}
worker.js:
self.onmessage = function (e) {
var data = e.data;
switch (data.cmd) {
case 'start':
taskStart(); //大量データ処理
postMessage('WORKER DO: ' + data.msg);
break;
case 'pause':
taskPause();
postMessage('WORKER DO: ' + data.msg);
break;
case 'stop':
postMessage('WORKER DO: ' + data.msg);
self.close(); //Web Workerの終了
break;
default:
postMessage('MESSAGE: ' + data.msg);
};
};
上記の例からわかるように、オブジェクトパラメーターを利用することで、プロセス間で柔軟な制御が可能です。また、worker が taskStart () で大量のデータを処理する場合、サブプロセスでのみ処理され、メインページにはブロッキングを引き起こしません。通常、大量のデータを処理するとプログラムの応答能力に悪影響を与えることがありますが、Web Worker はこのような方法でよりスムーズでリアルタイムな UI を提供できます。
###【共有ワーカー】
共有ワーカーは、同じオリジンの複数のページ間でスレッドを共有することができます。たとえば、同じオリジンのすべてのページやスクリプトが同じ共有スレッドと通信できます。インスタンス化とイベントリスナーの書き方は専用ワーカーとは少し異なります。メインページ:
var worker = new SharedWorker('shared-worker.js');
worker.port.onmessage = function(e) {
msg = 'Someone just said "' + e.data.message + '". That is message number ' + e.data.counter;
console.log(msg);
};
worker.port.postMessage('hello shared worker!');
shared-worker.js:
var counter = 0;
var connections = [];
onconnect = function(eConn) {
var port = eConn.ports[0]; // この接続の固有のポート
// メッセージがあるとすべての接続に通知する
port.onmessage = function(eMsg) {
counter++;
for (var i=0; i < connections.length; i++) {
connections[i].postMessage({
message: eMsg.data,
counter: counter
});
}
}
port.start();
connections.push(port);
}
このページを 2 つのウィンドウで開くと、最初のウィンドウには「Someone just said "Hello shared worker!" This is message number 1」と表示され、2 番目のウィンドウにも同じメッセージが表示されますが、後ろには「message number 2」と表示されます。
###【セキュリティとエラーチェック】
セキュリティ上の理由から、Web Worker は同一オリジンポリシーに従う必要があります。また、そのグローバルオブジェクトは Web Worker オブジェクト自体であり、this と self はどちらも Web Worker オブジェクトを参照します。
アクセスできるもの:
- navigator オブジェクト(appName、appVersion、platform、userAgent のみ)
- location オブジェクト(読み取り専用)
- XMLHttpRequest
- setTimeout ()、setInterval ()、clearTimeout ()、clearInterval () メソッド
アクセスできないもの:
- DOM(スレッドセーフではない)
- window オブジェクト
- document オブジェクト
- parent オブジェクト
Web Worker 内でエラーが発生した場合、worker.onerror を使用してエラーを検知できます。error イベントには次の 3 つのプロパティがあります:
- filename:エラーが発生したファイル名
- lineno:コードの行番号
- message:完全なエラーメッセージ
例:
worker.onerror = function(e) {
console.log(e.filename+"ERROR on line"+e.lineno+",msg:"+e.message);
}
###【その他の Web Worker の試み】
時間のかかる操作に対して、Web Worker はその役割を果たすことができます。たとえば、大量のデータのソートやピクセル単位のキャンバス計算などです。また、JSONP でデータをロードする際には、スクリプトタグを動的に作成し、ロードおよび実行するプロセスはすべてブロッキングですが、Web Worker は非同期にロードできます。これはより高速な方法でしょうか?この疑問を持って以下の実験を行いました。同じファイルを JSONP と Worker の方法でロードし、データの計算が返されるまでの遅延を計測しました。
function tryJsonp(){
var d = (new Date()).valueOf();
var jsonp=document.createElement("script");
jsonp.type="text/javascript";
jsonp.src="worker.js?_="+d;
document.getElementsByTagName("head")[0].appendChild(jsonp);
jsonp.onload = jsonp.onreadystatechange = function(){
if(!this.readyState||this.readyState=='loaded'||this.readyState=='complete'){
console.log('jsonp: '+ ((new Date()).valueOf() - d));
}
}
}
function tryWorker(){
var d = (new Date()).valueOf();
var worker = new Worker("worker.js");
worker.postMessage({'cmd': 'start', 'msg': 'start'});
worker.onmessage = function (event) {
console.log('web worker: '+ ((new Date()).valueOf() - d));
};
}
最初のロードは 1k のファイルを 5 回繰り返し行い、結果は次の通りです:
2 回目のロードは 1800k のファイルを行い、結果は次の通りです:
小さいデータの場合、jsonp の方が web worker よりも速いことがわかります。これは、worker オブジェクトのインスタンス化による影響かもしれません。一方、データが大きい場合、web worker のロードがより優れており、非同期にロードできます。
THE END.