Web Workersは、Javascriptの処理をバックグランドで実行するメカニズムを提供します。
ブラウザに限らず、多くのアプリケーションは「プロセス」と言う単位で動きます。
WEBブラウザの多くも、タブやウィンドウで分かれていても、ほとんどが「一つのプロセス」の中で実行されます。
そのため、一つのタブやウィンドウの処理が遅くなったり、クラッシュするとそのアプリケーション全体に影響が及びます。
そのため、「マルチプロセス」に対応したブラウザも存在します。(Google Chromeなど)
「マルチプロセス」にすることで、一つの処理が重くて遅くなったり、クラッシュしても他のタブやウィンドウには影響しないようになります。
なお、近年のOSのほとんどは「マルチプロセス」に対応しているため、そのアプリケーションではクラッシュしても他のアプリケーションには影響しないようになっています。
「マルチプロセス」になれば、同じブラウザ内でタブやウィンドウが違えば他の処理に影響されないのですが、同じタブやウィンドウ内 つまり一つのページを処理している間は、その処理が終わるのを待つ必要があります。
例えば、<head>内で呼び出される外部スクリプトファイルが大変大きなものが定義されているページを読み込む場合、外部スクリプトファイルの処理が完了するまで、ページの表示だけでなく、ブラウザの操作自体ができません。
これを解決する方法として「マルチスレッド」というものがあります。
「マルチスレッド」とは、1つのプロセス(アプリケーション)内で、同時並行に独立して処理を行うメカニズムです。
「マルチプロセス」は、それぞれのプロセスが完全に独立しているのに対し、「マルチスレッド」はそれぞれのスレッドがメモリ空間を共有します。
そのため「マルチプロセス」に比べメモリ消費を抑えられると共に、同じメモリ空間のデータを共有することができます。
しかし「マルチスレッド」はその制御、特にデータの競合や排他処理をうまく行わないとプロセス自体が動かなくなってしまいます。
このように「マルチスレッド」を行うプログラムは大変複雑なものとなります。
本題のWeb Workersは、「マルチスレッド」に似ています。
しかしWeb Workersは、メモリ空間のデータ - ブラウザ上のページ内容(windowオブジェクトやdocumentオブジェクトなど) を共有することはできません。
データの共有はできないのですが、その代わりにデータの競合や排他処理を行う必要がありません。
このようにスレッドに似たWeb Workersで処理する単位を「ワーカー」と呼びます。
このページでは、この「ワーカー」の利用方法について紹介致します。
各ブラウザの対応
Web Workersは、2011年8月現在以下のブラウザで対応しています。
FireFox 3.5~、Safari 4~、Opera 10.6~、Google Chrome 3~
(IEでは10~の予定)
まずはWeb Workersを使わなかった場合の処理と用意してみます。
次のサンプルスクリプトは、単純にfor文で繰り返し、累積値を算出します。
リスト1. 重い処理例
//通常の処理...ハングアップします。
function myStart01(me){
var op = document.getElementById('myOutput01');
op.innerHTML = "";
me.innerHTML = "処理中";
var s = 0;
var n = 3000000000;
for(var i=0;i<n;i++){
s = mySigma(s, i);
}
op.innerHTML = s;
me.innerHTML = "実行";
}
//累積結果を得る
function mySigma(s, i){
return s+i;
}
実装例
実行内容:
実行
※注意! この実装例は非常に時間が掛かる処理で、場合によりWEBブラウザは応答できなくなります。
なるべく実行しないでください。
次のサンプルスクリプトはWeb Workersを使った例です。
前項同様、単純にfor文で繰り返し、累積値を算出します。
処理内容はほぼ同じですが、Web Workersを使うことで、表示しているこのページと別途に実行されます。
そのため、処理中でもメニュー操作などを行うことができます。
Web Workersは、呼び出し側(親側)とワーカー側のファイルを分けて用意します。
new Worker(ワーカー定義スクリプトファイル);
Workerクラスから、Workersオブジェクトを作ります。
「ワーカー定義スクリプトファイル」は、別途ワーカーとして実行するスクリプトを用意します。
「ワーカー定義スクリプトファイル」は、相対URLでも絶対URLでも構いません。
つまり「http://...」「https://...」「ftp://...」でも可能です。
親側とワーカー側をつなぐのは「postMessage」メソッドです。
この「postMessage」メソッドで親とワーカー間でデータのやりとりをします。
(Workersオブジェクト).postMessage(メッセージ)
親側とワーカー側でデータのやりとりを行う唯一の方法です。
親側からは、Workersオブジェクトを用いてメッセージを送ります。
ワーカー側からは、自分を示す「self」オブジェクトを用いてメッセージを送ります。
メッセージは、スカラー値だけでなく、オブジェクト(配列を含む)でも対応します。
但し、メッセージは参照渡しではなく、コピー渡しとなります。
Web Workersのイベントは「onmessage」と「onerror」です。
(Workersオブジェクト).onmessage:messageイベント
Workersオブジェクトがメッセージを受け取ったときに発生するイベントです。
親側からは、Workersオブジェクトを用いてイベントを受け取ります。
ワーカー側からは、自分を示す「self」オブジェクトを用いてイベントを受け取ります。
postMessageメソッドで送られたメッセージはイベントの「data」プロパティで受け取れます。
(Workersオブジェクト).onerror:エラー処理イベント
Workersオブジェクトでエラーが発生したときに発生するイベントです。
親側からは、Workersオブジェクトを用いてイベントを受け取ります。
ワーカー側からは、自分を示す「self」オブジェクトを用いてイベントを受け取ります。
エラー内容はイベントの「message」プロパティで受け取れます。
リスト2. Web Workersの例(親側)
//Web Workersを利用
function myStart02(me){
var op = document.getElementById('myOutput02');
op.innerHTML = '';
// Workersオブジェクト
var worker = new Worker('wrkr01.js');
// messageイベントのハンドラをセット
worker.onmessage = function(evt){
var res = evt.data;
// ワーカーから返ってきた情報を表示
op.innerHTML = res;
me.onclick = function(){
myStart02(me);
}
me.innerHTML = "実行";
}
// errorイベントのハンドラをセット
worker.onerror = function(evt){
var msg = "";
msg += evt.message + "\n";
msg += "Line : " + evt.lineno;
alert(msg);
}
// ワーカーに処理開始のメッセージを送る
worker.postMessage('start');
// ボタンの処理を定義
me.innerHTML = "処理中(キャンセル)";
me.onclick = function(){
worker.terminate();
op.innerHTML = 'キャンセルされました';
me.onclick = function(){
myStart02(me);
}
me.innerHTML = "実行";
}
}
リスト3. Web Workersの例(ワーカー側)
// wrkr01.js の中身
//親からメッセージを送られてきたとき
self.onmessage = function(event){
//親から送られてきた命令
var cmd = event.data;
//命令の実行
switch(cmd){
case 'start':
myStart();
break;
}
}
//繰り返しの実行
function myStart(){
var s = 0;
var n = 3000000000;
try{
// n回加算
for(var i=0;i<n;i++){
s = mySigma(s, i);
}
self.postMessage(s);
}
catch(e){
// 例外処理
self.postMessage(' Error:' + e.message);
}
}
//累積結果を得る
function mySigma(s, i){
return s+i;
}
実装例
実行内容:
実行
「ワーカー定義スクリプトファイル」内で、jQueryなど外部スクリプトライブラリを使いたい場合、
<script src="外部スクリプトライブラリ"></script>
というわけには行きません。
そこで、「impoerScripts」メソッドを用いて外部スクリプトライブラリを呼び出します。
self.impoerScripts(Url,...);
外部スクリプトライブラリのURLを引数にして、呼び出します。
外部スクリプトライブラリのURLは、相対URLでも絶対URLでも構いません。
つまり「http://...」「https://...」「ftp://...」でも可能です。
複数の場合は、カンマ(,)区切りで指定できます。
Web Workersを使う場面はいろいろとあります。
以下は、その一例です。
Web Workersを用いることで、利用者にストレスを感じさせず、複雑な処理を行うことが出来るようになります。
活用シーン
画像フィルタ
<canvas>要素(※「ブラウザだけで図形を描く 」参照)などでフィルタ効果などの画像処理を行う場合に有効です。
画像処理は、大概 処理の重いものが多く、canvasだけでは処理を行う間、そのページ内の他の部分に影響を及ぼします。
そこで画像処理を別ワーカーで動かすことにより、処理を実施中でも他の操作が可能となります。
冗長かつ複雑な科学計算
円周率の計算など、再帰的な処理を必要とする複雑かつ冗長な科学計算を行う場合に有効です。
再帰的な処理は、その計算深度が冗長であるため処理時間が推測しにくい場合がほとんどです。
そのため、結果を待つ間 そのページの操作を止めてしまうことになりかねません。
Web Workersを活用することで、待ち時間を有効に使うことができます。
コンテンツの動的ロード
AJAXやRESTなど、外部コンテンツを動的にロードする場合に有効です。
昨今、RSSやtwitter、FacebookなどのSNSとの連携が多く用いられます。
それらの多くは、表示するページとは、別のドメイン下にあるものが多く、接続などのオーバーヘッドの大きいものがほとんどです。
そのため、同一プロセス内で動的なロードを行うと、その間 表示自体が待ち状態になりえます。
そのような場合、Web Workersを用いて、ロードをバックグラウンドで行うようにします。
そうすることで、表のページ処理とロード処理が分離することができます。