本週精讀的文章是 speedy-introduction-to-web-workers,是一篇 Web Workers 快速入門的文章,借精讀這篇文章的機會,談談對 Web Workers 的理解與運用。javascript
就像分工,你只負責編碼,而你的朋友負責設計,那你就能夠專心把本身的事情作好,並且更快速的完成任務。前端
本文經過一個比方,描述了 Web Workers 的兩大特徵:java
由於瀏覽器是單線程的,任何大量耗時的 JS 任務都會卡住界面,使瀏覽器沒法響應任何操做,這樣的用戶體驗很是糟糕。Web Workers 能夠將耗時任務拆解出去,下降主線程的壓力,避免主線程無響應。webpack
但 CPU 資源是有限的,Web Workers 並不能增長整體運行效率,算上通訊的損耗,總體計算效率會有必定的降低。git
const worker = new Worker("../src/worker.js");
複製代碼
上述代碼中,worker
就是一個 Web Workers 實例,執行的代碼是 ../src/worker.js
路徑下的文件。github
Web Workers 用來執行異步腳本,只要掌握了它與主線程通訊的方式,就能夠在指定時機運行異步腳本,並在運行完時將結果傳遞給主線程。web
const worker = new Worker("../src/worker.js");
worker.onmessage = e => {};
worker.postMessage("Marco!");
複製代碼
每一個 worker
實例經過 onmessage
接收消息,經過 postMessage
發送消息。後端
self.onmessage = e => {};
self.postMessage("Marco!");
複製代碼
和主線程代碼相似,在 Web Workers 代碼中,也是 onmessage
接收消息,這個消息來自主線程或者其它 Workers。也能夠經過 postMessage
發送消息。瀏覽器
worker.terminate();
複製代碼
文章內容就這麼多,是否是有寫太簡單了呢!筆者結合本身的使用經驗,再補充一些知識。架構
對象轉移就是將對象引用零成本轉交給 Web Workers 的上下文,而不須要進行結構拷貝。
這裏要解釋的是,主線程與 Web Workers 之間的通訊,並非對象引用的傳遞,而是序列化/反序列化的過程,當對象很是龐大時,序列化和反序列化都會消耗大量計算資源,下降運行速度。
上面的圖充分證實了,大對象傳遞,使用對象轉移各項指標都優於結構拷貝。
對象轉移使用方式很簡單,給 postMessage
增長一個參數,把對象引用傳過去便可:
var ab = new ArrayBuffer(1);
worker.postMessage(ab, [ab]);
複製代碼
瀏覽器兼容性也不錯:Currently Chrome 17+, Firefox, Opera, Safari, IE10+。更具體內容,能夠看 Transferable Objects: Lightning Fast!。
須要注意的是,對象引用轉移後,原先上下文就沒法訪問此對象了,須要在 Web Workers 再次將對象還原到主線程上下文後,主線程才能正常訪問被轉交的對象。
Web Workers 優點這麼大,但用起來須要在同域下建立一個 JS 文件實在不方便,尤爲在先後端分離作的比較完全的團隊,前端團隊能控制的僅僅是一個 JS 文件。那麼下面給出幾個不用 JS 文件,就建立 Web Workers 的方法:
worker-loader 是一個 webpack 插件,能夠將一個普通 JS 文件的所有依賴提取後打包並替換調用處,以 Blob 形式內聯在源碼中。
import Worker from "worker-loader!./file.worker.js";
const worker = new Worker();
複製代碼
上述代碼的魔術在於,轉化成下面的方式執行:
const blob = new Blob([codeFromFileWorker], { type: "application/javascript" });
const worker = new Worker(URL.createObjectURL(blob));
複製代碼
第二種方式由第一種方式天然帶出:若是不想用 webpack 插件,那本身經過 Blob 的方式建立也能夠:
const code = ` importScripts('https://xxx.com/xxx.js'); self.onmessage = e => {}; `;
const blob = new Blob([code], { type: "application/javascript" });
const worker = new Worker(URL.createObjectURL(blob));
複製代碼
看上去代碼更輕量一些,不過問題是當遇到複雜依賴時,若是不能把依賴都轉化爲腳本經過 importScripts
方式引用,就沒法訪問到主線程環境中的包。若是真的遇到了這個問題,能夠用第一種 webpack 插件的方式解決,這個插件會自動把文件全部依賴都打包進源碼。
爲何 postMessage 會造成隊列,爲何要管理它?
首先在 Web Workers 架構設計上就必須作成隊列,由於調用 postMessage
時,對應的 Web Workers 不必定完成了初始化,因此瀏覽器底層必須管理一個隊列,在 Web Workers 初始化完畢時,依次消費,這樣才能確保任什麼時候候發出的 postMessage
都能被 Web Workers 接收到。
其次,爲何要手動維護這個隊列,緣由可能取決於以下幾點:
postMessage
還沒來得及消費,就不要發送新的消息,或者丟棄新的消息,這時候須要經過雙向通訊拿到 Web Workers 的執行結果回執,手動控制隊列。如上圖所示,對於每次用戶輸入都要進行的 SQL Parser 很耗時,及時放在 Web Workers 也可能致使將 Workers 撐爆到無響應,這是不只要使用多 Workers 緩衝池,還要對待執行隊列進行過濾,由於用戶永遠只關心最後一次輸入的 Parser 結果。
因爲 Web Workers 運算被卡住時,除了銷燬 Worker 沒有別的辦法,而銷燬 Worker 的成本比較高,不能對每個用戶輸入都銷燬並新建 Web Workers,因此利用 Workers 緩衝池,當緩衝池滿了,新的消費隊列又進來的時候,能夠銷燬所有 Workers 緩衝池,換一批新緩衝池從新消費用戶輸入。
Web Workers 是拆解異步計算的好幫手,vscode 網頁版也經過 Web Workers 異步完成代碼提示和高亮,筆者有對比過,發現 Web Workers 性能提高很是明顯。
管理好你的 Web Workers 消息隊列,謹防同步計算讓 Web Workers 失去響應!創建一個智能的消息隊列,根據業務需求設計一個最好的隊列消費模型吧!
若是你想參與討論,請點擊這裏,每週都有新的主題,週末或週一發佈。前端精讀 - 幫你篩選靠譜的內容。