初識Web Worker
原文發布在我的個人博客
初識Web Worker|以太空間
一、概述
眾所周知,不同於Java/C#這些編程語言,JavaScript作為一門為瀏覽器而生的語言,採用的是單線程模型,也就是說,所有任務排成一個隊列,一次只能做一件事。隨著電腦計算能力的增強,尤其是多核CPU的出現,這一點帶來很大的不便,無法充分發揮JavaScript的潛力。不過2009年Node.js出現後,JavaScript也可以依靠Node.js的cluster模塊以多線程的方式運行,但是這種方式必須要在Node環境中才行,那麼在瀏覽器中如何以多線程的方式運行JavaScript呢?
Web Worker 是HTML5標準的一部分,這一規範定義了一套 API,它允許一段JavaScript程序運行在主線程之外的另外一個線程中,換句話說,允許主線程將一些任務分配給子線程。在主線程運行的同時,子線程在後台運行,兩者互不干擾。等到子線程完成計算任務,再把結果返回給主線程。因此,每一個子線程就好像一個「工人」(worker),默默地完成自己的工作。這樣做的好處是,一些高計算量或高延遲的工作,被worker線程負擔了,所以父進程(通常是UI進程)就會很流暢,不會被阻塞或拖慢。
Web Worker有以下幾種工作線程:
- 專用線程(Dedicated Worker):只能為一個頁面所使用,只能與創造它們的父進程通信。
- 共享線程(Shared Worker):可以被多個頁面(必須同域)獲取並使用。
- Service Worker:一個在網路應用與瀏覽器或網路層之間的代理層,它可以攔截網路請求,使得離線訪問成為可能。
Web Worker有以下幾個特點:
- 同域限制:子線程載入的腳本文件,必須與主線程的腳本文件在同一個域。
- DOM限制:子線程所在的全局對象,與父進程不一樣,它無法讀取網頁的DOM對象,即document、window、parent這些對象,子線程都無法得到。(但是,navigator對象和location對象可以獲得。)
- 腳本限制:子線程無法讀取網頁的全局變數和函數,也不能執行alert和confirm方法,不過可以執行setInterval和setTimeout,以及使用XMLHttpRequest對象發出AJAX請求。
- 文件限制:子線程無法讀取本地文件,即子線程無法打開本機的文件系統(file://),它所載入的腳本,必須來自網路。
二、基礎API
1. 新建和啟動子線程
首先在父進程(假設父進程文件是main.js)中調用構造函數(Worker),傳入子進程腳本的文件名。
// File: main.jsconst worker = new Worker(worker.js);
這個子進程腳本必須來自網路端,如果下載失敗的話,子進程也啟動不了了。
子線程新建之後,並沒有啟動,必需等待主線程調用postMessage
方法,即發出信號之後才會啟動。postMessage
方法的參數,就是主線程傳給子線程的信號。它可以是一個字元串,也可以是一個對象。
// File: main.jsworker.postMessage("Hello World");worker.postMessage({ method: echo, args: [Work]});
注意:只要符合父線程的同源政策,Worker線程自己也能新建Worker線程。Worker線程可以使用XMLHttpRequest進行網路I/O,但是XMLHttpRequest對象的responseXML和channel屬性總是返回null。
2. 父子進程的事件監聽
首先在父進程中對子進程進行消息監聽
// File: main.jsworker.onmessage = function(e) { let { data } = e; console.log(data);};// 或者worker.addEventListenr(function (e) { console.log(e.data);});  ```然後在子進程中對父進程進行消息監聽```js// File: worker.jsself.onmessage = function(event) { let method = event.data.method; let args = event.data.args; console.log(method, args);};// 或者self.addEventListener(message, function(e) { console.log(e.data);});
3. 父子進程的數據通信
父進程向子進程發送消息
// File: main.jsworker.postMessage(Hello, My honey bady);
子進程向父進程發送消息
// File: worker.jsself.postMessage(Hello, My daddy);
4. 錯誤處理
父線程可以監聽子線程是否發生錯誤。如果發生錯誤,會觸發主線程的error事件。
// File: main.jsworker.onerror(function(event) { console.log(event);});// 或者worker.addEventListener(error, function(event) { console.log(event);});
5. 關閉子線程
使用完畢之後,為了節省系統資源,我們必須在主線程調用terminate方法,手動關閉子線程。
// File: main.jsworker.terminate();
也可以子線程內部關閉自身。
// File: worker.jsself.close();
三、圖解worker模型
webKit載入並執行worker線程的流程如下圖所示
以下內容來自AlloyTeam團隊
1. worker線程的創建的是非同步的
代碼執行到let worker = new Worker(task.js)
時,在內核中構造WebCore::JSWorker對象(JSBbindings層)以及對應的WebCore::Worker對象(WebCore模塊),根據初始化的url地址task.js
發起非同步載入的流程;主線程代碼不會阻塞在這裡等待worker線程去載入、執行指定的腳本文件,而是會立即向下繼續執行後面代碼。
2. postMessage消息交互由內核調度
main.js中,在創建woker線程後,立即調用了postMessage方法傳遞了數據,在worker線程還沒創建完成時,main.js中發出的消息,會先存儲在一個臨時消息隊列中,當非同步創建worker線程完成,臨時消息隊列中的消息數據複製到woker對應的WorkerRunLoop的消息隊列中,worker線程開始處理消息。在經過一輪消息來回後,繼續通信時, 這個時候因為worker線程已經創建,所以消息會直接添加到WorkerRunLoop的消息隊列中。
四、worker線程數據通訊方式
主線程與子線程數據通信方式有多種,通信內容,可以是文本,也可以是對象。需要注意的是,這種通信是拷貝關係,即是傳值而不是地址,子線程對通信內容的修改,不會影響到主線程。事實上,瀏覽器內部的運行機制是,先將通信內容串列化,然後把串列化後的字元串發給子線程,後者再將它還原。
參考鏈接
[1] AlloyTeam 深入理解Web Worker[2] 阮一峰 Web Worker
推薦閱讀:
※推薦 5 款超好用的 Chrome 瀏覽器插件,文末有從別人的電腦移植插件的方法
※Windows 10 Pro 64bit 環境下瀏覽器的使用
※第3期:瀏覽器之戰(下)
※我就發個圖
※除了UC還有啥可選?盤點安卓上的良心瀏覽器