精讀《談談 Web Workers》

1 引言

本週精讀的文章是 speedy-introduction-to-web-workers,是一篇 Web Workers 快速入門的文章,借精讀這篇文章的機會,談談對 Web Workers 的理解與運用。javascript

2 概述

就像分工,你只負責編碼,而你的朋友負責設計,那你就能夠專心把本身的事情作好,並且更快速的完成任務。前端

本文經過一個比方,描述了 Web Workers 的兩大特徵:java

  1. 高效。
  2. 並行。

由於瀏覽器是單線程的,任何大量耗時的 JS 任務都會卡住界面,使瀏覽器沒法響應任何操做,這樣的用戶體驗很是糟糕。Web Workers 能夠將耗時任務拆解出去,下降主線程的壓力,避免主線程無響應。webpack

但 CPU 資源是有限的,Web Workers 並不能增長整體運行效率,算上通訊的損耗,總體計算效率會有必定的降低。git

建立 Web Workers

const worker = new Worker("../src/worker.js");
複製代碼

上述代碼中,worker 就是一個 Web Workers 實例,執行的代碼是 ../src/worker.js 路徑下的文件。github

收發消息

Web Workers 用來執行異步腳本,只要掌握了它與主線程通訊的方式,就能夠在指定時機運行異步腳本,並在運行完時將結果傳遞給主線程。web

主線程接收發 Web Workers 消息

const worker = new Worker("../src/worker.js");

worker.onmessage = e => {};

worker.postMessage("Marco!");
複製代碼

每一個 worker 實例經過 onmessage 接收消息,經過 postMessage 發送消息。後端

Web Workers 收發主線程消息

self.onmessage = e => {};

self.postMessage("Marco!");
複製代碼

和主線程代碼相似,在 Web Workers 代碼中,也是 onmessage 接收消息,這個消息來自主線程或者其它 Workers。也能夠經過 postMessage 發送消息。瀏覽器

銷燬 Web Workers

worker.terminate();
複製代碼

文章內容就這麼多,是否是有寫太簡單了呢!筆者結合本身的使用經驗,再補充一些知識。架構

3 精讀

對象轉移(Transferable Objects)

對象轉移就是將對象引用零成本轉交給 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 再次將對象還原到主線程上下文後,主線程才能正常訪問被轉交的對象。

如何不用 JS 文件建立 Web Workers

Web Workers 優點這麼大,但用起來須要在同域下建立一個 JS 文件實在不方便,尤爲在先後端分離作的比較完全的團隊,前端團隊能控制的僅僅是一個 JS 文件。那麼下面給出幾個不用 JS 文件,就建立 Web Workers 的方法:

webpack 插件 - worker-loader

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));
複製代碼

Blob URL

第二種方式由第一種方式天然帶出:若是不想用 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 隊列

爲何 postMessage 會造成隊列,爲何要管理它?

首先在 Web Workers 架構設計上就必須作成隊列,由於調用 postMessage 時,對應的 Web Workers 不必定完成了初始化,因此瀏覽器底層必須管理一個隊列,在 Web Workers 初始化完畢時,依次消費,這樣才能確保任什麼時候候發出的 postMessage 都能被 Web Workers 接收到。

其次,爲何要手動維護這個隊列,緣由可能取決於以下幾點:

  • 業務緣由,前面的 postMessage 還沒來得及消費,就不要發送新的消息,或者丟棄新的消息,這時候須要經過雙向通訊拿到 Web Workers 的執行結果回執,手動控制隊列。
  • 性能緣由,通常 Web Workers 都會被用來執行耗時的同步運算,若是運算時間比較長,那短時間塞入多個消息隊列是沒有意義的。

如上圖所示,對於每次用戶輸入都要進行的 SQL Parser 很耗時,及時放在 Web Workers 也可能致使將 Workers 撐爆到無響應,這是不只要使用多 Workers 緩衝池,還要對待執行隊列進行過濾,由於用戶永遠只關心最後一次輸入的 Parser 結果。

因爲 Web Workers 運算被卡住時,除了銷燬 Worker 沒有別的辦法,而銷燬 Worker 的成本比較高,不能對每個用戶輸入都銷燬並新建 Web Workers,因此利用 Workers 緩衝池,當緩衝池滿了,新的消費隊列又進來的時候,能夠銷燬所有 Workers 緩衝池,換一批新緩衝池從新消費用戶輸入。

4 總結

Web Workers 是拆解異步計算的好幫手,vscode 網頁版也經過 Web Workers 異步完成代碼提示和高亮,筆者有對比過,發現 Web Workers 性能提高很是明顯。

管理好你的 Web Workers 消息隊列,謹防同步計算讓 Web Workers 失去響應!創建一個智能的消息隊列,根據業務需求設計一個最好的隊列消費模型吧!

5 更多討論

討論地址是:精讀《談談 Web Workers》 · Issue #108 · dt-fe/weekly

若是你想參與討論,請點擊這裏,每週都有新的主題,週末或週一發佈。前端精讀 - 幫你篩選靠譜的內容。

相關文章
相關標籤/搜索