メインコンテンツへ移動

Web Workers を理解する

無料2015-12-02#JS#web worker#JavaScript Web Worker#js Worker#js SharedWorker#Web Workers API

Web Workers とは何か、何に役立つか、どのように使用するか

一.Web Workers とは何か

Web Workers は、タスクをバックグラウンドスレッドで実行することをサポートするメカニズムです。その通り、Web Worker は動作が制限されたマルチスレッドを提供します

ブラウザ内の js の実行環境は依然としてシングルスレッドですが、マルチスレッドはブラウザには昔から存在していました。例えば、非同期 XHR は動作が制限されたスレッドであり、メインスレッドから独立しており、内置のメッセージメカニズムを通じて通信し、一般的なマルチスレッドの並行問題は存在しません

Web Workers はメインスレッドから独立した context を提供し、この context 内では制限された API を使用できます(DOM 操作は禁止、Cache にアクセスできない可能性がありますなど)。マルチスレッドの最大の利点は UI をブロックしないことで、マルチスレッドを使用して時間のかかるタスク(例えばオーディオ/ビデオデコード、およびそれに基づくデータ処理)を完了できます。API が許可されていれば、Web Workers を使用して XHR を自行実装することさえできます

P.S.Web Worker が Cache にアクセスすることを許可するかどうかについては、現在(2015/11/29)まだ議論があります。Chrome43 と FF はサポートし、IE と Safari のサポート状況は不明です

少し余談:Google の Gears プラグインは Worker Pool API を提案しました。それが Web Workers の「原型」です。当初はブラウザの機能を強化することを望んでいました。例えばオフラインブラウジングのサポート(オフラインでキャッシュページにアクセスし、再オンライン後にオフライン操作を提交)などですが、現在(2015/11/29)はすでに廃止されています

Google Gears API

The Google Gears API is no longer available. Thank you for your interest.

二.Web Workers は何に役立つか

バックグラウンドスレッドの最大の役割は do stuff です。バックグラウンドスレッドをサポートすることは、ブラウザがより多くの重いタスクを負担できることを意味します。サーバー側が担っていた一部の演算(例えばソート、エンコード/デコード、テンプレート生成)をクライアント側に移譲し、サーバー側の圧力を軽減できます。ユーザー体験に影響を与えることなく(サーバー側の結果を待つのも、ローカルの別のスレッドの結果を待つのも、どちらも待機であり、後者の方がより速い可能性があります)

筆者は Web Workers には以下の適用シナリオがあると考えています:

  • オーディオ/ビデオデコード

audioContext.decodeAudioData などの操作を試したことがあれば、「重い作業」ができるバックグラウンドスレッドを切実に必要としていることがわかるでしょう

  • 画像前処理

例えばアバターアップロード前のトリミング、さらにはウォータ���マーク追加、合成、モザイク追加など。クライアント側で完了できれば、大量の一時ファイル転送を回避できます

  • ソートなどのデータ処理アルゴリズム

サーバー圧力を軽減

  • クライアントテンプレート

例えば markdown、またはサーバー側が JSON を返し、クライアント側が取得後にバックグラウンドスレッドに渡して解析し、テンプレート HTML を適用してページを生成。これらの操作をすべてクライアント側で完了すれば、転送する必要のあるものがより少なくなります

三.Web Workers の使用方法

話すことができるサンプルコードは以下の通り:

//--- 主ページ
if (window.Worker) {
    var worker = new Worker('worker.js');
    var data = {a: 1, b: [1, 2, 3], c: 'string'};
    worker.postMessage(data);
    worker.onmessage = function(e) {
        console.log('main thread received data');
        console.log(e.data);

        // 接到消息立即停止 worker,onerror 将不会触发
        // worker.terminate();
        // terminate 之后收不到后续消息,但 post 不报错
        // worker.postMessage(1);
    }
    worker.onerror = function(err) {
        console.log('main thread received err');
        console.log(err.message);
        // 阻止报错
        err.preventDefault();
    }
}
//---end 主ページ

//---worker.js
// 可以引入其它依赖
// importScripts('lib.js');
// importScripts('a.js', 'b.js');
onmessage = function(e) {console.log(self); // 看看 global 变量身上有些什么
    var data = e.data;
    console.log('worker received data');
    console.log(data);

    var res = data;
    res.resolved = true;

    postMessage(res);

    setTimeout(function() {
        throw new Error('error occurs');

        // close,立即停止,相当于主线程中的 worker.terminate()
        // close();
    }, 100);
};
//---end worker.js

注意

  1. worker の global context は windowではなくself です。self も一連のインターフェースを提供し、self.JSONself.Mathself.console などを含みます。最も直観的な違いは document オブジェクトがなくなりますが、locationnavigator は残っています

  2. Worker の原型は DedicatedWorkerGlobalScope で、SharedWGS とは異なります。Worker は専用であり、Worker を作成したスクリプトのみが自分が作成した worker にアクセスできます

  3. worker 内では DOM 操作は許可されていませんが、WebSocket、IndexedDB、XHR などをサポートします。各ブラウザのサポート状況には差異があります。詳細は Functions and classes available to Web Workers - Web APIs | MDN を参照してください

注意:XHR は使用できますが、XHR オブジェクトの responseXMLchannel 属性は常に null を返します

  1. メインスレッドと worker スレッドはメッセージの送受信方法が一致します(postMessage で送信、onmessage/onerror で受信、データは MessageEvent の data 属性から取得)。スレッド間で传递されるのは値の copy であり、共有参照ではありません

  2. IE10 と FF は worker 内での Worker の new をサポートします。これらの worker が親ページと同源である必要があります

  3. サポート性问题については、機能検出を行うことを推奨します

    if (window.Worker) {
        // new Worker...
    }
    
  4. importScripts は他の js ファイルを導入できます。外部ファイル内のグローバル変数は self に貼り付けられ、worker 内で直接参照できます。importScripts は同期的であり、ダウンロードと実行完了後に次の行を実行します

四.Shared Worker

上記で注目した Worker は dedicated worker(専用 worker)に属し、worker オブジェクトを作成した context のみが worker へのアクセス権を持ちます。Shared Worker は cross context の worker 共有をサポートします。例えば window と iframe、iframe と iframe

それらは共有属性を持つため、異なるウィンドウ内のアプリケーションの同じ状態を維持でき、異なるウィンドウのページは同一の共有 worker スクリプトを通じて状態を維持および報告します

localStorage を使用しても状態同期を実現できます。zxx 先輩の例:Page Visibility( 网页可见性) API とログイン同期誘導ページ实例ページ。興味があれば原文をご覧ください:Page Visibility( ページ可視性) API 紹介、微拡張

筆者が考えられる Shared Worker の唯一の適用シナリオは、アプリケーション状態同期メカニズムの実装かもしれません。cookie、localStorage も状態を保存できますが、Shared Worker は一連の複雑な状態制御メカニズムを実現できます。簡単に言えば、cookie と localStorage にはデータのみを置けますが、Shared Worker にはデータを置けるだけでなく、コードも実行できます

さらに、Shared Worker はあまり役に立たないようです。結局 new Worker できる已经很不錯了。锦上添花のものの役割は人を驚かせないでしょう

サンプルコードは以下の通り:

//--- 主ページ
if (window.SharedWorker) {
    var sworker = new SharedWorker('sharedworker.js');

    // 1.onmessage 隐式调用 start
    // sworker.port.onmessage = function(e) {
    //     console.log('main thread received data:');
    //     console.log(e.data);
    // }
    // 2.或者 addEventListener 再显式调用 start
    sworker.port.addEventListener('message', function(e) {
        console.log('main thread received data:');
        console.log(e.data);
    });
    sworker.port.start();

    sworker.port.postMessage([1, 2, 3]);
}
//---end 主ページ

//---sharedworker.js
var count = 0;

onconnect = function(e) {
    // 记录连接数
    count++;

    var port = e.ports[0];

    // 1.onmessage 隐式调用 start
    port.onmessage = function(e) {
        //!!! console.log 失效了
        console.log('will not occur in console');
        
        port.postMessage({receivedData: e.data, count: count});

        //!!! 错误不会抛回给 sharedworker 创建者,静默失败
        //!!! 如果 sharedworker 本身有语法错误,也会静默失败,而且在这之前的 postMessage 也将无效
        // a*; // 制造一个语法错误
        
        setTimeout(function() {
            throw new Error('error occurs');
        }, 100);
    };
    // 2.或者 addEventListener 再显式调用 start
    // port.addEventListener('message', function(e) {
    //     //!!! console.log 失效了
    //     console.log('will not occur in console');

    //     port.postMessage('sharedworker received data: ' + e.data);
    // });
    // port.start();
};
//---end sharedworker.js

オンライン DEMO:testSharedWorker DEMO

テスト方法:���記のリンクを開き、console を見て count が 1 であることを確認。どのようにリフレッシュしても 1 のまま。別のタブで該ページを開くと、count が 2 になる。その後リフレッシュを繰り返すと、count が 5 になる。之前的なタブに戻り、リフレッシュすると、count が 6 になる...

五.スレッド安全问题

スレッド安全の面では、並行によって引き起こされる各种问题は存在しません。Worker スレッドの動作は制限されているためです(worker は非スレッド安全なコンポーネントと DOM にアクセスできません)

六.その他の Worker

上記で議論した Web Workers の他に、これらの Worker があります:

  • ServiceWorkers(プロキシサーバーに類似し、オフラインサービスを提供)

  • ChromeWorker(ブラウザプラグイン開発で使用可能な Worker)

  • Audio Workers(オーディオ処理をサポート、ブラウザサポートなし、2015/11/29 現在)

参考資料

コメント

コメントはまだありません

コメントを書く