2012年4月7日土曜日

WebWorkerはJSの救世主となれるのか?

「JavaScriptはシングルスレッドしか扱えない」というのは、JavaScriptをいじったことがある人には馴染み深い定説でしょう。一見するとsetIntervalやsetTimeoutなどでマルチスレッドな作業がこなせるように見えるJSですが、実はあれはそれらの関数に渡したコールバックオブジェクトをキューに追加しておいて今実行している関数を抜けたらそのキューから次の関数を呼び出すという実装をしているだけなのです。

そんなJavaScriptですが、コーディングしてChromeやSafariなどでWebInspectorを開いてデバッグをしているとグローバルスコープに気になるオブジェクトを見かけることがあります。
Workerとあります。みなさんもC++などでマルチスレッドなプログラムを組んだり、GUIつきのプログラムを書いているときに「ワーカースレッド」という言葉を耳にしたことはないでしょうか。このWorkerがその「ワーカー」と同じなら、何やらマルチスレッドできそうな「匂い」を感じませんか?感じますよね?
そう、このWorkerコンストラクタこそ、我らがJavaScripterの救世主(になる予定)のマルチスレッド化の秘密兵器なのです。
さて、そんなWorkerオブジェクトですが、一体どうやって使うんでしょうか。結論から言ってしまうと次のように書けば使えます。
//main.js
var worker = new Worker("calc.js");    //Workerの処理を記述したファイルのパスを引数にコンストラクタを呼び出す

worker.onmessage = function(event){    //Workerからメッセージが返ってきた時の処理を設定する
   var data = event.data;
   data.forEach(function(num){
      write(num + ", ");
   });
};

var defs = {a_n : [0, 1], to : 20};
worker.postMessage(defs);               //Workerにデータを渡して処理を開始させる
//calc.js
onmessage = function(event){            //メインスレッドからデータを受け取って処理する
   var data = event.data, results = [data.a_n[0], data.a_n[1]];
   for(var i = 0; i < data.to; ++i){ //フィボナッチ数列を計算して配列に収める
      results.push(results[i] + results[i + 1]);
   }
   postMessage(results);                //処理した結果をメインスレッドに返す
};
WebWorkerを使うには最低限.jsファイルが2つ必要です。一つは、Workerを呼び出すメインのファイル(上記の例のmain.jsに当たります)、もうひとつはWorkerがすべき処理を記述したファイル(上記の例でのcalc.js)です。大まかな流れはコメントに書いてあるとおりなので、詳しい解説は不要でしょう。
しかし、その中でも注意すべき点が2つあります。まず、calc.js内のonmessage関数の定義の部分に注目してください。まるでグローバル変数のような定義をしていますが、これはWorkerのスコープとメインスレッドのスコープが分離しているためです。つまり、このonmessage変数はメインスレッドからは参照不可能なわけです。
またこれに伴って通常、ユーザーエージェントでJavaScriptを実行するとグローバルスコープにwindowやDOMなどのオブジェクトが定義されているものですが、Workerが動作するスコープにはこれらのオブジェクトは一切定義されていません。そのため、WorkerからDOM操作などはできません。これは、自らGUIつきのプログラムを書いたことがある人はよく解ると思いますが、好き勝手にGUIをいじるコードを書いていると複数のスレッドが同時にGUIをいじってしまって整合性に破綻をきたしてしまう場合があるので、メインスレッド以外からは一切GUIをいじれないようにするための措置です。
2つ目は、メインスレッドとWorkerでやり取りされるデータの形式です。この二者間のデータのやり取りは、JSON形式でやり取りされているようなので関数を含むオブジェクトをpostMessageに渡すと関数が失われてしまいます。このため、メソッドによる振る舞いの定義が必要なオブジェクトを扱う処理をWorkerでこなすには少し手を加える必要があります。
以上、自サイトで公開しているプログラム内でWebWorkerを使う機会があったので、備忘録も兼ねて記事にしてみました。



後日追記:もうちょっと実用的なサンプルをjsdo.itに公開しました。

0 件のコメント:

コメントを投稿

なにか意見や感想、質問などがあれば、ご自由にお書きください。