聊聊webWorker

先看幾個例子

本例子是經過經過紅點展現地球上的地震帶,數據來自於地質探測局
經過console.log看到數據運算所耗的時間
不使用 webworker No web workers - all on main thread
使用一條 webworker One web worker
使用兩條 Two web workers
使用八條 Eight web workers
使用20條 20 web workersjavascript

結論:是? // 帶着思考看下去html

背景

JavaScript引擎是單線程運行的,JavaScript中耗時的I/O操做都被處理爲異步操做,它們包括鍵盤、鼠標I/O輸入輸出事件、窗口大小的resize事件、定時器(setTimeout、setInterval)事件、Ajax請求網絡I/O回調等。當這些異步任務發生的時候,它們將會被放入瀏覽器的事件任務隊列中去,等到JavaScript運行時執行線程空閒時候纔會按照隊列先進先出的原則被一一執行,但終究仍是單線程。
clipboard.pnghtml5

雖然JS運行在瀏覽器中,是單線程的,每一個window一個JS線程,但瀏覽器不是單線程的,例如Webkit或是Gecko引擎,均可能有以下線程:java

javascript引擎線程
界面渲染線程
瀏覽器事件觸發線程
Http請求線程

不少人以爲異步(promise async/await),都是經過相似event loop在日常的工做中已經足夠,可是若是作複雜運算,這些異步僞線程的不足就逐漸體現出來,好比settimeout拿到的值並不正確,再者假如頁面有複雜運算的時候頁面很容易觸發假死狀態,
爲了有多線程功能,webworker問世了。不過,這並不意味着 JavaScript 語言自己就支持了多線程,對於 JavaScript 語言自己它還是運行在單線程上的, Web Worker 只是瀏覽器(宿主環境)提供的一個能力/APInode

簡介

Web Worker 是HTML5標準的一部分,這一規範定義了一套 API,它容許一段JavaScript程序運行在主線程以外的另一個線程中。工做線程容許開發人員編寫可以長時間運行而不被用戶所中斷的後臺程序, 去執行事務或者邏輯,並同時保證頁面對用戶的及時響應,能夠將一些大量計算的代碼交給web worker運行而不凍結用戶界面,後面會有案例介紹git

類型

Web workers可分爲兩種類型:專用線程dedicated web worker,以及共享線程shared web workerDedicated web worker隨當前頁面的關閉而結束;這意味着Dedicated web worker只能被建立它的頁面訪問。與之相對應的Shared web worker能夠被多個頁面訪問。在Javascript代碼中,「Work」類型表明Dedicated web worker,而「SharedWorker」類型表明Shared web worker
Shared Worker則能夠被多個頁面所共享(同域狀況下)github

如何建立

Web Worker的建立是在主線程當中經過傳入文件的url來實現的。以下所示:web

let webworker = new Worker('myworker.js');

返回的是webworker實例對象,該對象是主線程和其餘線程的通信橋樑
主線程和其餘線程能夠經過ajax

onmessage: 監聽事件
postmessage: 傳送事件

相關的API進行通信
案例代碼以下json

//主線程 main.js
var worker = new Worker("worker.js");
worker.onmessage = function(event){
    // 主線程收到子線程的消息
};
// 主線程向子線程發送消息
worker.postMessage({
    type: "start",
    value: 12345
});

//web worker.js
onmessage = function(event){
   // 收到
};
postMessage({
    type: "debug",
    message: "Starting processing..."
});

相關demo

如何終止

若是在某個時機不想要 Worker 繼續運行了,那麼咱們須要終止掉這個線程,能夠調用 在主線程workerterminate 方法 或者在相應的線程中調用close

// 方式一 main.js 在主線程中止方式 
var worker = new Worker('./worker.js');
...
worker.terminate();

// 方式2、worker.js
self.close()

錯誤機制

提供了onerror API

worker.addEventListener('error', function (e) {
  console.log('MAIN: ', 'ERROR', e);
  console.log('filename:' + e.filename + '-message:' + e.message + '-lineno:' + e.lineno);
});

// event.filename: 致使錯誤的 Worker 腳本的名稱;
// event.message: 錯誤的信息;
// event.lineno: 出現錯誤的行號;

sharedWorker

對於 Web Worker ,一個 tab 頁面只能對應一個 Worker 線程,是相互獨立的;
SharedWorker 提供了能力可以讓不一樣標籤中頁面共享的同一個 Worker 腳本線程;
固然,有個很重要的限制就是它們須要知足同源策略,也就是須要在同域下;
在頁面(能夠多個)中實例化 Worker 線程:

// main.js

var myWorker = new SharedWorker("worker.js");

myWorker.port.start();

myWorker.port.postMessage("hello, I'm main");

myWorker.port.onmessage = function(e) {
  console.log('Message received from worker');
}
// worker.js
onconnect = function(e) {
  var port = e.ports[0];

  port.addEventListener('message', function(e) {
    var workerResult = 'Result: ' + (e.data[0]);
    port.postMessage(workerResult);
  });
  port.start();
}

在線demo

父子線程

線程中再建立線程

環境與做用域

Worker 線程的運行環境中沒有 window 全局對象,也沒法訪問 DOM 對象,因此通常來講他只能來執行純 JavaScript 的計算操做。可是,他仍是能夠獲取到部分瀏覽器提供的 API 的:

setTimeout(), clearTimeout(), setInterval(), clearInterval():有了設計個函數,就能夠在 Worker : 線程中能夠再建立worker;
XMLHttpRequest : 對象:意味着咱們能夠在 Worker 線程中執行 ajax 請求;
navigator 對象:能夠獲取到 ppName,appVersion,platform,userAgent 等信息;
location 對象(只讀):能夠獲取到有關當前 URL 的信息;
Application Cache
indexedDB
WebSocket、
Promise、

庫或外部腳本引入和訪問

在線程中,提供了importScripts方法
若是線程中使用了importScripts 通常按照如下步驟解析

一、解析 importScripts方法的每個參數。
二、若是有任何失敗或者錯誤,拋出 SYNTAX_ERR 異常。
三、嘗試從用戶提供的 URL 資源位置處獲取腳本資源。
四、對於 importScripts 方法的每個參數,按照用戶的提供順序,獲取腳本資源後繼續進行其它操做。
// worker.js
importScripts('math_utilities.js'); 
onmessage = function (event) 
 { 
     var first=event.data.first; 
     var second=event.data.second; 
     calculate(first,second); // calculate 是math_utilities.js中的方法 
 };

也能夠一次性引入多個

//能夠多起一次傳入
importScripts('script1.js', 'script2.js');

XMLHttpRequest

onmessage = function(evt){
    var xhr = new XMLHttpRequest();
    xhr.open("GET", "serviceUrl"); //serviceUrl爲後端j返回son數據的接口
    xhr.onload = function(){
    postMessage(xhr.responseText);
    };
    xhr.send();
}
jsonp
// 設置jsonp
function MakeServerRequest() 
{
    importScripts("http://SomeServer.com?jsonp=HandleRequest");
} 

// jsonp回調
function HandleRequest(objJSON) 
{
    postMessage("Data returned from the server...FirstName: " 
                  + objJSON.FirstName + " LastName: " + objJSON.LastName);
} 

// Trigger the server request for the JSONP data 
MakeServerRequest();

通信原理

從一個線程到另外一個線程的通信其實是一個值拷貝的過程,其實是先將數據JSON.stringify以後再JSON.parse。主線程與子線程之間也能夠交換二進制數據,好比File、Blob、ArrayBuffer等對象,也能夠在線程之間發送。可是,用拷貝方式發送二進制數據,會形成性能問題。好比,主線程向子線程發送一個50MB文件,默認狀況下瀏覽器會生成一個原文件的拷貝。爲了解決這個問題,JavaScript容許主線程把二進制數據直接轉移給子線程,轉移後主線程沒法再使用這些數據,這是爲了防止出現多個線程同時修改數據的問題,這種轉移數據的方法,叫作Transferable Objects。
不過如今不少瀏覽器支持transferable objects(可轉讓對象) ,這個技術是零拷貝轉移,能大大提高性能,
能夠指定傳送的數據全都是零拷貝

var abBuffer = new ArrayBuffer(32);
aDedicatedWorker.postMessage(abBuffer, [abBuffer]);

也能夠 指定某個是 使用 零拷貝

var objData = {
   "employeeId": 103,
   "name": "Sam Smith",
   "dateHired": new Date(2006, 11, 15),
   "abBuffer": new ArrayBuffer(32)
};
aDedicatedWorker.postMessage(objData, [objData.abBuffer]);

工做線程生命週期

工做線程之間的通訊必須依賴於瀏覽器的上下文環境,而且經過它們的 MessagePort 對象實例傳遞消息。每一個工做線程的全局做用域都擁有這些線程的端口列表,這些列表包括了全部線程使用到的 MessagePort 對象。在專用線程的狀況下,這個列表還會包含隱式的 MessagePort 對象。
每一個工做線程的全局做用域對象 WorkerGlobalScope 還會有一個工做線程的線程列表,在初始化時這個列表爲空。當工做線程被建立的時候或者擁有父工做線程的時候,它們就會被填充進來。
最後,每一個工做線程的全局做用域對象 WorkerGlobalScope 還擁有這個線程的文檔模型,在初始化時這個列表爲空。當工做線程被建立的時候,文檔對象就會被填充進來。不管什麼時候當一個文檔對象被丟棄的時候,它就要從這個文檔對象列舉裏面刪除出來。

性能測試

初始化測試
// 部分機器webwoker初始化時間
Macbook Pro: 2 workers, 0.4 milliseconds on average
Macbook Pro: 4 workers, 0.6 milliseconds on average
Nexus 5: 2 workers, 6 milliseconds on average
Nexus 5: 4 workers, 15 milliseconds on average (border-line UI jank)
傳輸速度測試

一、普通json/object
clipboard.png

二、tranferable objects

clipboard.png
可見 transferable objects傳輸速度要高不少

部分典型的應用場景以下

1) 使用專用線程進行數學運算
Web Worker最簡單的應用就是用來作後臺計算,而這種計算並不會中斷前臺用戶的操做
2) 圖像處理
經過使用從<canvas>或者<video>元素中獲取的數據,能夠把圖像分割成幾個不一樣的區域而且把它們推送給並行的不一樣Workers來作計算
3) 大量數據的檢索
當須要在調用 ajax後處理大量的數據,若是處理這些數據所需的時間長短很是重要,能夠在Web Worker中來作這些,避免凍結UI線程
4) 背景數據分析
因爲在使用Web Worker的時候,咱們有更多潛在的CPU可用時間,咱們如今能夠考慮一下JavaScript中的新應用場景。例如,咱們能夠想像在不影響UI體驗的狀況下實時處理用戶輸入。利用這樣一種可能,咱們能夠想像一個像Word(Office Web Apps 套裝)同樣的應用:當用戶打字時後臺在詞典中進行查找,幫助用戶自動糾錯等等。

限制

一、不能訪問DOM和BOM對象的,Location和navigator的只讀訪問,而且navigator封裝成了WorkerNavigator對象,更改部分屬性。沒法讀取本地文件系統
二、子線程和父級線程的通信是經過值拷貝,子線程對通訊內容的修改,不會影響到主線程。在通信過程當中值過大也會影響到性能(解決這個問題能夠用transferable objects
三、並不是真的多線程,多線程是由於瀏覽器的功能
四、兼容性
5 由於線程是經過importScripts引入外部的js,而且直接執行,實際上是不安全的,很容易被外部注入一些惡意代碼
六、條數限制,大多瀏覽器能建立webworker線程的條數是有限制的,雖然能夠手動去拓展,可是若是不設置的話,基本上都在20條之內,每條線程大概5M左右,須要手動關掉一些不用的線程纔可以建立新的線程(相關解決方案
七、js存在真的線程的東西,好比SharedArrayBuffer

clipboard.png

js的多線程庫

一、tagg2

參考文獻:[1] https://www.html5rocks.com/zh...
[2] http://www.alloyteam.com/2015...
[3] https://typedarray.org/concur...
[4] http://www.andygup.net/advanc...
[5] https://developer.mozilla.org...
[6] http://coolaj86.github.io/htm...
[7] http://www.xyhtml5.com/webwor...

相關文章
相關標籤/搜索