はじめに

過去記事「モダンJavaScript入門/非同期処理のモダンな書き方」でも、記述したように、Webブラウザ上のJavaScriptコードは、「単一スレッド」によるイベントループで動作しています。

その為、1度に1つの処理しか実行できません。

同期処理や重たい処理を実行すると、ユーザーには、まるでフリーズもしくは、ハングアップしたかのように見えてしまいます。

理由は、画面の描画やユーザーの入力を受け付ける部分も、同じスレッドのイベントループの中に存在するからです。

その為、メインスレッドでは、重たい処理を避け、イベント中心の処理や、ES6 で追加された非同期処理を利用したコードが求められます。

ただ、それにも限界があり、画像処理やデータ圧縮、同期APIを使った処理など、どうしても回避しきれない問題も存在します。

その問題を解決する仕組みとして、2009年に標準化が始まった API である Web Worker が存在します。


Web Worker とは

Web Worker は、メインスレッドとは独立した JavaScript 実行環境を作成します。それぞれが独自のイベントループを持ちます。

この仕組みを利用することで、メインのイベントループを止めずに、CPU 負荷の高い処理や、長時間ブロックする同期 API を Web Worker にまかせることが可能となります。

👉つまり、 Web Worker は JavaScriptにおいて、メインスレッドとは独立したバックグラウンド処理を実現するための重要な基盤技術です。

通常の JavaScript は1つのメインスレッドで動作しています。このスレッドの中で、イベントループにより、画面の描画や、ユーザーからの入力などを処理しています。

┌─────────────────────────────────────┐
│ メインスレッド                       │
│                                     │
│  ┌────────────────▶┐                │
│  │  イベントループ  │                │
│  │      ┌──────────┿──────────┐     │
│  │      ▼          ▼          ▼     │
│  │   画面描画  ユーザー入力   処理A   │
│  │      │          │          │     │
│  │      └──────────┿──────────┘     │
│  │                 ▼                │
│  └◀────────────────┘                │
└─────────────────────────────────────┘

例えば、処理Aで、下記のような重い処理をすると

for(let i = 0; i < 10000000000; i++){

}
  • ボタンが押せない
  • スクロールできない
  • 画面が固まる

という状態になります。

これを防ぐために生まれたのがWeb Workerです。

┌─────────────────────────────────────┐                 ┌────────────────────────┐
│ メインスレッド                       │                 │ バックグラウンドスレッド │
│                                     │                 │                        │
│  ┌────────────────▶┐                │                 │  ┌──────────────────┐  │
│  │  イベントループ  │                │                 │  │  Web Worker      │  │
│  │      ┌──────────┿──────────┐     │                 │  │                  │  │
│  │      ▼          ▼          ▼     │                 │  │                  │  │
│  │   画面描画  ユーザー入力   処理A ────── 処理依頼 ──────▶ │   重い処理        │  │
│  │      │          │          │     │                 │  │                  │  │
│  │      └──────────┿──────────┘     │                 │  └──────────────────┘  │
│  │                 ▼                │                 │                        │
│  └◀────────────────┘                │                 │                        │
└─────────────────────────────────────┘                 └────────────────────────┘

つまり、重い処理をメインスレッドのイベントループから切り離し、並列稼働するバックグラウンドスレッドの Web Workerにまかせることで、イベントループの停止を防ぐことができます。

結果、画面がフリーズもしくは、ハングアップしたようにみえる事態を防ぐことができます。


メインスレッドと Web Worker の違い

メインスレッドは、Web ブラウザで HTML が表示されたタイミングで自動で起動されているスレッドです。

一方、Web Worker は、メインスレッドからバックグラウンドで動作する Worker スレッドで起動されます。メインスレッドと違い、画面とのつながりがありません。

オブジェクト メインスレッド Web Worker
window
document
イベントループ

その為、DOM 操作など、画面を制御する処理は、Web Worker では行えません。

ただ、メインスレッドも Web Worker もそれぞれ個別のイベントループを持っています。


Web Worker でできること

Web Worker でも JavaScript は普通に実行できます。使えないのは主に DOM 関連 API です。

前述のとおり、Web Worker には、window オブジェクトも、document オブジェクトも存在しない為、DOM 操作など、それらに起因することは、直接行うことができません。

基本的に、メインスレッドに依頼する形で行うことになります。

それでは、Web Worker では、どのようなことが行えるのでしょうか。

例えば、

  • 画像加工
  • 動画変換
  • PDF生成
  • ZIP圧縮
  • 暗号化
  • AI推論
  • 大量JSON解析
  • 大量CSV解析
  • 外部通信
    • fetch
    • WebSocket
  • タイマー処理
    • setTimeout()
    • setInterval()
  • OPFSの同期アクセス
  • IndexedDBとの大量データ処理

このようなCPU負荷が高い処理や、画面の応答性を保ちたい処理で特に効果を発揮します。

これらの「重たい処理」をメインスレッドで行う UI 操作に影響を与えずに、行うことが可能です。

実は、OPFS の同期 API と Web Worker は非常に相性が良いです。OPFSについては、過去記事、「データ保存技術/OPFSとは①」を参照してください。


Web Worker の種類

本来、Web Worker には、その起動方法により、2種類の異なるタイプが存在します。

  • Dedicated Worker
  • Shared Worker

■ Dedicated Worker

これは、いわゆる普通の Web Worker です。

通常1つの頁に1つのメインスレッドが存在し、そのメインスレッドに結びついています。

メインスレッドは複数の Web Worker を起動できる為、

      メインスレッド
        │
  ┌─────────┿─────────┰────────
  │         │         │
WorkerA  WorkerB  WorkerC  ・・・

メインスレッドからみた場合、Dedicated Worker は、1対多です。しかし、Dedicated Worker からみた場合、

WorkerA │ メインスレッド

1対1 となります。

Dedicated Worker は、通常はメインスレッドとのpostMessage()による通信を行います。

また、該当頁が終了すると、一緒に終了します。

■ Shared Worker

Dedicated Worker と異なり、複数ページから共有される Web Workerです。

Tab1
      \
       Worker
      /
Tab2

メインスレッドと Web Worker は、

   Tab1の              Tab2の
メインスレッド       メインスレッド
     │                   │
     └─────────┰─────────┴────────
               │
     ┌─────────┿─────────┰────────
     │         │         │
   WorkerA  WorkerB  WorkerC  ・・・

多対多となります。

複数の頁をまたがった処理を1つの Web Worker で担うことが可能である為、複数Tabを利用した開発では、メモリ節約などの効果が期待できます。

しかし、最近は利用頻度はあまり高くありません。それは、複数Tabにまたがる処理の必要性が少ないことや、その管理の煩雑さが起因しています。

Shared Worker が複数Tabに結びついている関係上、最後の接続元が切断されるまでSharedWorkerは生存します。正しく管理しないとメモリリーク等の問題が発生する可能性もあります。

👉 本記事でも、特別な説明がない限り、Shared Worker ではなく、Dedicated Worker について説明するものとします。

■ Service Worker との違い

Worker という言葉を聞くと、PWA などで利用する Service Worker を思い浮かべる方も多いと思います。

※ PWA(Progressive Web Apps)は、Web アプリをネイティブアプリのように、オフラインでも使用可能にする技術です。

しかし、Web Worker と Service Worker は、メインスレッドとは、別に平行動作する点は同じですが、全くの別物です。

Web Worker Service Worker
メインスレッドから起動 ブラウザが必要に応じて起動
計算する ネットワークを制御する
UIを軽くする オフライン対応
バックグラウンド計算 PWAやキャッシュ
ページに従属 ページとは独立して動作

あくまで、

  • Web Worker は、「重い計算係」を担う仕掛け
  • Service Workerは、「ネットワーク係」を担う仕掛け

です。


まとめ

  • Web Workerは別スレッドで動く。
  • postMessage()でメインスレッドと通信する。
  • documentやwindowにはアクセスできない。
  • 重い処理をWorkerに任せることで画面が固まらなくなる。
  • OPFSの同期API(createSyncAccessHandle())はWorker内でのみ使用できる。

次回予告

今回は、Web Workerがどのような仕組みで動作し、何ができるのかを中心に解説しました。

次回は、実際にWeb Workerを作成し、postMessage()を使ったメインスレッドとの通信方法や、Workerのライフサイクルについて詳しく見ていきます。