跳到主要內容
黯羽輕揚每天積累一點點

理解 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,而是selfself也提供一系列接口,包括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裡 new Worker,只要這些 worker 和父頁面是同源的

  3. 支持性問題,建議做特性檢測

    if (window.Worker) {
        // new Worker...
    }
    
  4. importScripts可以引入其它 js 文件,外部文件中的全局變量將被粘在self上,worker裡可以直接引用。importScripts是同步的,下載並執行完畢執行下一行

##四.Shared Worker

上面我們關注的 Worker 屬於 dedicated worker(專用 worker),只有創建 worker 對象的 context 有 worker 的訪問權。而 Shared Worker 支持跨 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)

###參考資料

評論

暫無評論,快來發表你的看法吧

提交評論