0
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 3 years have passed since last update.

【JavaScript】WebWorkerのメッセージングにBroadcastChannelを使うメリットとデメリット

Posted at

こんにちわ。

今回は、みんな大好きWebWorkerのお話です。
新しいインターフェイスとその使いかたを知ったので、まとめてみようと思います。

普通のWebWorker

WebWorkerを使うとき、普通はWorkerに備え付けられたメッセージングインターフェイスでスレッド間の通信を行うと思います。

メインスレッド
const worker = new Worker("./worker.js");
worker.addEventListner("message", ({data})=>{});
worker.postMessage("ほげほげ", []);
ワーカースレッド
// self => DedicatedWorkerGlobalScope
self.addEventListener("message", ({data})=>{
    self.postMessage("ほげほげ");
});

普通に使うなら、これで充分な場合がほとんどです。
特にDedicatedWorkerの場合、そのコンテキスト内で処理を完結させるケースがほとんどなので、困ることはないと思います。

しかし、例えばバックエンドサーバーとクライアントアプリケーションの間でAPIの仲介を行う、半永続的なワーカースレッドを生やすとします。

この場合、アプリケーションのあらゆるコンテキストからワーカースレッドへアクセスされることが予想されます。

ワーカースレッドを複数のコンテキストで共有したい場合、まずSharedWorkerが浮かぶと思います。

しかしこのSharedWorkerは、コンテキストを跨いだメッセージングを行うためにDedicatedWorkerでは隠蔽されていたMessagePortというメッセージングインターフェイスを操作する必要があり、これが少々厄介です。

メインスレッド
const worker = new SharedWorker("./worker.js");
worker.port.start();
worker.port.addEventListener("message", ({data})=>{});
worker.port.postMessage("ほげほげ", []);
ワーカースレッド
// self => SharedWorkerGlobalScope
self.addEventListener("connect", ({ports})=>{
    ports[0].addEventListener("message", ({data})=>{
        ports[0].postMessage("ほげほげ", []);
    });
    ports[0].start();
});

* 厳密にはDedicatedWorkerのメッセージングインターフェイスにもMessagePortは存在しますが、コンテキストはひとつなのでユーザーからは隠蔽され暗示的に操作されます。

BroadcastChannel

BroadcastChannelは、コンテキストを跨いだメッセージングを簡単に行うためのインターフェイスです。

コンストラクタでチャネルを指定でき、そのチャネルが存在しない場合は作成し、存在する場合は参加します。
作成もしくは参加した後は、ブロードキャストと名にある通りそのチャネルへ参加しているコンテキストへメッセージを配信できます。

メインスレッド内同士はもちろんワーカースレッドやiframeなども横断してアクセスできます。

BroadcastChannelも他のメッセージングインターフェイスと同様にChannel Messaging APIがベースとなっているため、実際のコーディングも似たような感じとなっています。

BroadcastChannel.prototype.postMessage()で、そのチャネルへ接続しているすべてのコンテキストへ向けてメッセージを配信します。
BroadcastChannel.prototype.onmessageで、配信されたすべてのメッセージを購読します。
BroadcastChannel.prototype.close()で、チャネルへの接続を終了し、コンテキストをガベージコレクタに回収してもらえるようにします。

複数のコンテキスト
// "my-channel"は存在しないため作成される
const ctx1 = new BroadcastChannel("my-channel");
ctx1.addEventListner("message", ({data})=>{
    ctx1.close();
});
ctx1.postMessage("ほげほげ");

// "my-channel"は存在するため参加される
const ctx2 = new BroadcastChannel("my-channel");
ctx2.addEventListner("message", ({data})=>{
    ctx2.close();
});
ctx2.postMessage("ほげほげ");

Worker × BroadcastChannel

WebWorkerでスレッド間通信を行う場合、備え付けのメッセージングインターフェイスを使用するのではなく、代わりにBroadcastChannelを使うことができます。

メインスレッド
new Worker("./worker.js");

const ctx1 = new BroadcastChannel("worker-channel");
ctx1.addEventListener("message", ({data})=>{
    ctx1.close();
});
ctx1.postMessage("ほげほげ");

const ctx2 = new BroadcastChannel("worker-channel");
ctx2.addEventListener("message", ({data})=>{
    ctx2.close();
});
ctx2.postMessage("ほげほげ");
ワーカースレッド
const bc = new BroadcastChannel("worker-channel");
bc.addEventListener("message", ({data})=>{
    bc.postMessage("ほげほげ");
});

本来であればDedicatedWorkerは複数のコンテキストを跨ぐことはできませんがBroadcastChannelと組み合わせることでMessagePortを隠蔽しつつコンテキストを跨ぐことができます。

なおWorkerコンストラクタは、宣言した時点でスクリプト解析とスレッド生成と実行が開始されるため、ワーカースレッドを強制停止(Worker.prototype.terminate())させる必要がなければ、インスタンス変数を充てがう必要はありません。

メリット

まずSharedWorkerと違いMessagePortを操作する必要がないため、複数のコンテキストが存在していたとしてもメッセージングを簡単に実装できます。

また、ワーカースレッドとメッセージングのオブジェクトを分離することにより、コードの見通しが良くなるという利点も存在します。

ひとつのワーカースレッドに複数のチャネルを設けて集中処理こさせることも可能ですし、メッセージを受け取って処理するスレッドをワーカースレッドからメインスレッドへ移す場合もBroadcastChannelのブロックを丸ごと移動させるだけで済みます。

特に最近のコンポーネント志向なフロントエンドフレームワークにおいては、強力な武器となるでしょう。

デメリット

BroadcastChannelは比較的ローレベルでシンプルなメッセージングインターフェイスのため、やりとりするメッセージに識別子を埋め込むなどして、目的のメッセージのみを購読するためのロジックを用意する必要があります。

また、最大のデメリットはBroadcastChannelにはTransferableインターフェイスが存在しません。
やりとりされるメッセージは全てコピーされます。
つまり、巨大なバイナリバッファのやりとりは不向きとなります。

0
1
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?