HTML5 Web Workers 入門

Web Workers概要

JavaScriptのコードをバックグラウンドで処理させる事ができる。別々のスレッドでJavaScriptを実行できるのでマルチコアのCPUを有効活用するアプリケーションを作れる。

対応ブラウザ(2010/9現在)

バックグラウンド?

ブラウザは単一のスレッド(= UI スレッド)で実行されているので、JSによる演算とDOMの操作が同じスレッド上で実行されている。
そのため、処理に時間がかかってしまうとDOMの操作(= UIの変更)にも影響が出る。最悪の場合にはプログラムの停止が求められる。

UIスレッド

UIスレッドは単純なキューイングシステムで、タスクはアイドル状態になるまでキューに並ぶ仕組み。UIスレッドのタスクにはUIの変更も、コードの実行も含まれる。

注意しないといけないのは、入力によっては新たにキューに加わるタスクを生成する可能性があるということ。

<!DOCTYPE html>
<html>
<head>
  <meta charset="utf-8" />
  <title>UI thread test</title>
</head>
<body>
  <button id="trig">click</button>

  <script type="text/javascript">
    document.getElementById('trig').addEventListener('click', function() {
      var div = document.createElement('div');
      div.innerHTML = "Hi!";
      document.body.appendChild(div);
    }, false);
  </script>
</body>
</html>

ボタンがクリックされると

  • ボタンがクリックされた時の見た目の変化を起こすタスク
  • 定義したイベントリスナの実行

という2つのタスクがキューに追加されて、この順番で処理が行われる。

イベントリスナでは、divを生成してbodyに追加する処理を行っているので、コードの実行中にこの処理が新たにタスクとしてキューに追加される。

タスクの実行中にはUIの更新が即実行されない他、更新するための新しいタスクが新たにキューに並ぶ可能性があるが、実際のところ、ほとんどのブラウザではユーザの操作を邪魔しない様に、JavaScriptのコード実行中はコードを終了させる事を優先してUIスレッドにタスクを追加しない様になっている。

とりあえず使ってみる

Web Workerオブジェクトが生成されると、postMessage APIを使ってメッセージのやり取りができる様になる。

workers.html(main.js) <->worker.jsでメッセージをやり取りする。

workers.html

<!DOCTYPE html>
<html lang="ja">
<head>
  <meta charset="UTF-8" />
  <title>Web Workers test</title>
  <style>
  p#response {
    color: red;
  }
  </style>
</head>
<body>
<h1>Web Workers Sample</h1>
<p id="support"></p>
<div>
  <a id="start" href="#">start</a>
  <a id="stop" href="#">stop</a>
</div>
<p id="response"></p>
<script type="text/javascript" src="main.js"></script>
</body>
</html>


main.js

var main = {
  init: function() {
    if(this.chkCompatible()) {
      this.initWorker();
    }
  },

  // ブラウザ対応のチェック
  chkCompatible: function() {
   if(typeof(Worker !== "undefined")) {
     document.getElementById("support")
             .innerHTML = "This browser supports Web Workers!";
     return true;
   }

   return false;
  },
  
  initWorker: function() {
    // workerとして実行したいJSファイルを相対/絶対パスで指定する
    // 同一出身のファイルのみ可能
    var worker = new Worker('worker.js');

    document.getElementById('start').addEventListener('click', function() {
      // workerへメッセージ送信
      worker.postMessage('message to worker');
    }, true);
    
    document.getElementById('stop').addEventListener('click', function() {
      // workerの停止
      // worker自身で停止する事ができないので呼び出しもとで停止させる
      worker.terminate();
      console.log('terminate worker:', worker);
    }, true);

    //workerからのメッセージに応答する
    worker.addEventListener('message', this.messageHandler, true);
    worker.addEventListener('error', this.errorHandler, true);
  },
  
  messageHandler: function(e) {
    document.getElementById('response').innerHTML = e.data;
  },
  
  errorHandler: function(e) {
    console.log(e.message, e);
  }
}

window.onload = function() {
  main.init();
};


worker.js

function messageHandler(e) {
  postMessage('worker.js got: ' + e.data);
}

addEventListener('message', messageHandler, true);


この辺のサンプルの方が役に立ちそう。
http://blog.mozbox.org/post/2009/04/10/Web-Workers-in-action