什么是woker
官方的解釋是這樣的:
worker是一個對象,通過構造函數Worker創建,參數就是一個js文件的路徑;文件中的js代碼將運行在主線程之外的worker線程;
var jsFileURI = JS_FILE_PATH; // js文件路徑 var worker = new Worker(jsFileURI);
worker運行在另一個全局上下文中(self),這個全局上下文不同於window,所以不能在woker中訪問window和DOM;
該線程分為兩種:dedicated worker和shared worker;dedicated worker只能被初始化它的js上下文中使用;shared worker可以在多個js上下文中使用。通常使用的worker是dedicated worker,它的工作情況可以通過chrome的調試工具查看。
為什么引入woker?
前端開發者應該知道瀏覽器中JS和UI公用一個線程,JS計算過程中,不能響應UI;如果遇到計算量比較大的任務,如操作圖像像素時,會造成用戶行為得不到響應。Web Worker 是為了解決 JavaScript 在瀏覽器環境中沒有多線程的問題。支持 Web Worker 的瀏覽器會額外提供一個 JavaScript Runtime 供 Web Worker 使用。它的最佳使用場景是執行一些開銷較大的數據處理或計算任務。
woker是怎么工作的?
Web Worker 使用起來非常簡單,在“主線程”中執行如下操作即可創建一個 Worker 實例,通過監聽 onmessage 事件獲取消息,通過 postMessage 發送消息:
“主線程”和Worker 之間通過 postMessage 發送消息,通過監聽 onmessage 事件來接收消息,從而實現二者的通信。
如下圖所示:
核心代碼如下:
主線程中代碼
var worker = new Worker('worker.js'); worker.onmessage = function (e) { var data = e.data; } var messageData = { message: 'hello worker!' }; worker.postMessage(messageData);
worker.js 中的代碼如下:
self.onmessage = function(e) { var messages = e.data; // e.data為{message: 'hello worker!'} var workerResult = {}; // do something ... postMessage(workerResult); }
使用woker的幾個tips
(1)使用多少個worker?
遇到復雜的計算,需要開啟多少worker才合適呢?一般的做法是參考navigator.hardwareConcurrency 這個屬性,它表示機器支持的並行最大任務數。還有一種動態檢測 Worker 數量的方法,有興趣的話可以看:https://github.com/oftn-oswg/core-estimator。
(2)優化woker與主線程通信開銷
該段主要參考百度地圖技術博客(https://mp.weixin.qq.com/mp/getmasssendmsg?__biz=MzIzNDE0NjMzOQ==#wechat_webview_type=1&wechat_redirect)。
Worker 與“主線程”之間的數據傳遞默認是通過結構化克隆(Structured Clone)完成的。數據量較大時,克隆過程會比較耗時,這會影響 postMessage 和 onmessage 函數的執行時間。
解決的辦法一是先通過 JSON.stringify 將對象序列化,接收之后再用 JSON.parse 還原。因為:stringfiy + 傳遞字符串的耗時 < 傳遞對象的耗時 。
代碼如下:
// 操作像素 var imageData = context.createImageData(img.width, img.height); var work = new Worker('./cal.js'); var data = { data: imageData.data, width: imageData.width, height: imageData.height }; // 將傳遞的參數轉換成字符串 work.postMessage(JSON.stringify(data));
還有一種避開克隆傳值的方法,就是使用Transferable Objects,主要是采用二進制的存儲方式,采用地址引用,解決數據交換的實時性問題;Transferable Objects支持的常用數據類型有ArrayBuffer和ImageBitmap;
使用方法如下:
// 操作像素 var imageData = context.createImageData(img.width, img.height); var work = new Worker('./cal.js'); // 轉化為類型數組進行傳遞 var int8s = new Int8Array(imageData.data); var data = { data: int8s, width: imageData.width, height: imageData.height }; // 在postMessage方法的第二個參數中指定transferList work.postMessage(data, [data.data.buffer]);
經測試,使用arrayBuffer之后,傳遞數據所需的時間為1ms,極大地提高了數據傳輸的效率。