はじめに
前回、Web Worker の基本的な事項について、説明してきました。
今回から、Web Worker の使い方について、数回にわたり説明していきたいと思います。
まずは、Web Worker が、起動後、どのようなサイクルで動作しているのかを簡単に説明します。
Web Worker のライフサイクル
一般的なバックグラウンドスレッドの Web Worker は、大きく分けて、
生成
│
▼
初期化
┌──────▶│
│ 待機
│ │
│ ▼
│ メッセージ受信
│ │
│ ▼
│ 処理
│ │
│ ▼
│ メッセージ送信
│ │
└───────┥
│
▼
終了
というイベントループの流れで動作します。
初期化後、メインスレッドからのメッセージによる依頼を受けて、処理を行い、処理完了後、結果をメインスレッドに返すという繰り返し動作をイベントループで行います。
このイベントループは、メインスレッドのイベントループから完全に独立しており、平行して動作します。
Web Worker の起動/停止
Web Worker は、メインスレッドから Web API を通じて、起動/停止が可能です。
■ Web Worker の起動
Web Worker の起動は簡単です。メインスレッドにて、Web API を使い、Web Worker オブジェクトを生成するだけです。
例えば
- main.js
メインスレッド用の JavaScript
- worker.js
Web Worker 用の JavaScript
という2つのスクリプトがあるとします。
main.jsで、
const worker = new Worker("worker.js");
と記述するだけで、バックグラウンドスレッドが起動され、worker.js のスクリプトが実行されます。
ブラウザは、この API が発行されると、
- Worker用の実行環境を作成する
- worker.jsを読み込む
- JavaScriptを実行する
という処理を行います。
■ Web Worker の停止
Web Worker の停止は、メインスレッド、Web Worker 自身のいづれからも行うことが可能です。
メインスレッドからの停止は、開始と同様に、Web API を使い、Web Worker オブジェクトを停止するだけです。
const worker = new Worker("worker.js");
:
worker.terminate();
この API を受け取ると、ブラウザは、Web Worker をできるだけ速やかに終了します。
※ Web Worker が、処理途中であっても、その時点で停止してしまうので、注意が必要です。処理の区切りで停止したい場合などは、後述の Web Worker 自身による終了を行う必要があります。
Web Worker 自身で終了を行う場合は、処理完了などの区切りの良いタイミングで、
self.close();
を実行するだけです。
後述のメッセージの送受信と組み合わせることで、メインスレッドからの終了の要求に応じて、区切りの良いタイミングで終了させるということが実現可能です。
※ self.close(); 以降に書かれたコードは実行されないので、注意が必要です。
メインスレッドと Web Worker のメッセージ送受信
Web Worker は、メインスレッドから起動されます。その為、当然のように、メインスレッドと Web Worker は、オブジェクトや変数を共有しているように見えますが、通常は、どちらも許可されていません。
というのも、Web Worker は、バックグラウンドスレッドで稼働しており、メインスレッドとは全く別の JavaScript 実行環境だからです。そのため、メモリ空間も全くの別ものとなります。
では、メインスレッドと Web Worker は連携することはできないのでしょうか?
そんなことはありません。メインスレッドと Web Worker はメッセージを使った通信により、お互いデータを受け渡したり、連絡を取り合うことが可能です。
その仕組みとして postmessage() が存在します。
postMessage() では、
- メインスレッド ⇒ Web Worker
- Web Worker ⇒ メインスレッド
双方向でのメッセージの送受信が可能です。
例:
main.js
const worker = new Worker("worker.js"); // Web Worker 起動
worker.onmessage = (event)=>{
console.log(event.data);
}
worker.postMessage("こんにちは"); // Web Worker にメッセージ送信
worker.js
self.onmessage = (event)=>{ // メインスレッドからのメッセージ受信
console.log(event.data);
self.postMessage("受け取りました"); // メインスレッドにメッセージ送信
}
出力
こんにちは ・・・ Web Workerが出力
受け取りました ・・・ メインスレッドが出力
メッセージで送れるもの
postMessage() では、下記のように、多くのもが送付可能です。
- オブジェクト
- 配列
- ArrayBuffer(コピー)
- JSON
- 文字列
- 数値
- Boolean
- Map
- Set
送付されたデータは、基本的には参照ではなく、コピーしたものが渡されます。
👉 これを 構造化複製 と呼びます。
例えば
main.js
const obj = {
value:10
};
const worker = new Worker("worker.js");
worker.postMessage(obj);
obj.value = 100;
worker.js
self.onmessage = (event)=>{
console.log(event.data);
}
出力
10
👉 Web Worker には、送信した時点のオブジェクトがコピーされて渡されます。
これは、Structured Clone Algorithm という仕組みです。
※ ただし、ArrayBufferなど一部のオブジェクトはコピーではなく「所有権の移動(Transfer)」もできます。これについては次回、「モダンJavaScript入門/Web Worker の使い方②」で説明します。
Web Worker 起動直後にメッセージ送信しても大丈夫なのか?
もし、Web Worker が onmessage() を登録する前に postMessage() したらそのメッセージはどうなるのでしょうか?
結論から言うと、メッセージは欠落せずに、Web Worker に届きます。
例えば、
メインスレッドで Web Workerを起動し、直後にメッセージを送信したとします。
const worker = new Worker("worker.js");
worker.postMessage("Hello");
この時、 Web Worker が、まだ読み込み中、もしくは、まだ onmessage() を設定していなかった場合、
メインスレッド
│ postMessage()
▼
Web Worker
まだ受信準備できていないとなり、 「メッセージが消えてしまうのでは?」 という疑問がわきます。
では、実際にはどうなるのでしょうか?
実際には、ブラウザが Web Worker 用のメッセージキューを持っており、それが緩衝役となり、
メインスレッド
│ postMessage()
▼
┌─ Workerのメッセージキュー
│
│ Web worker
│ worker.js初期化
│ onmessage設定
└───▶ Workerのメッセージキューから順番に取り出して処理
メッセージの欠落を防止します。
例えば、メインスレッドで、Web Worker を起動し、A , B , C の順でメッセージを送信したとします。
const worker = new Worker("worker.js");
worker.postMessage("A");
worker.postMessage("B");
worker.postMessage("C");
Web Worker がまだ、self.onmessage = ... を登録していない。もしくは、まだ worker.js をダウンロード途中だったとしても、内部では、Web Worker のメッセージキューに、
[A] [B] [C]
と並びます。その後、Web Worker にて、
self.onmessage = (event) => {
console.log(event.data);
};
が登録されると、Web Worker のメッセージキューから A , B , C が順番に取り出され、処理されます。
たとえ、Web Worker の初期化で、
// 重い初期化
for(let i=0;i<1000000000;i++){
}
// ここでやっと設定
self.onmessage = ...
のような重い初期化が行われたとしても、それが終わるまでは、メッセージはキューに溜まっています。
だから欠落しません。
ただし、ここで注意が必要です。
もし、 Web Worker が、throw new Error("初期化失敗"); などのエラーで初期化中に、終了してしまった場合、キューに残っていたメッセージは Web Worker 自体が終了するので処理されません。
これは JavaScript のイベントループと同じ考え方です。
例えば
button.addEventListener(...)
このように、postMessage() は単なる関数呼び出しではなく、ブラウザ内部のメッセージキューを介した非同期通信として実現されています。
そのため、Web Worker の起動直後にメッセージを送信しても、通常はメッセージが失われることはありません。
(つづく)