初識Web Worker

原文發布在我的個人博客

初識Web Worker|以太空間?

blog.io-code.com圖標

一、概述

眾所周知,不同於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還有啥可選?盤點安卓上的良心瀏覽器

TAG:HTML5 | 多線程 | 網頁瀏覽器 |