Web Worker

Javascript運(yùn)行在單線程環(huán)境中,雖然單線程避免了多線程的難點(diǎn),但缺點(diǎn)也很明顯,如果某個(gè)任務(wù)耗時(shí)很久,就無法處理其他任務(wù),會(huì)讓用戶感覺瀏覽器卡住了。所以瀏覽器中除了JS引擎線程,還有其他線程,比如 GUI 渲染線程、http 請(qǐng)求線程、事件觸發(fā)線程等,而通過事件循環(huán)機(jī)制對(duì)setTimeout/setInterval、ajax和dom事件的異步處理,就需要多個(gè)線程的參與。
HTML5引進(jìn)了 Web Worker,可讓JS在后臺(tái)運(yùn)行,執(zhí)行耗時(shí)長(zhǎng)的任務(wù),而不影響主頁面代碼的執(zhí)行。
1.定義:Web Worker 是HTML5標(biāo)準(zhǔn)的一部分,允許一段JavaScript程序運(yùn)行在主線程之外的另外一個(gè)線程中。Web Worker 規(guī)范中定義了兩類工作線程,分別是專用線程Dedicated Worker和共享線程 Shared Worker,其中,Dedicated Worker只能為一個(gè)頁面所使用,而Shared Worker可以被多個(gè)頁面所共享,本文以前者為例。
2.示例:
主頁面代碼 main.js

 // 一個(gè)較大的數(shù)組,其元素由隨機(jī)數(shù)構(gòu)成,傳到worker中去異步處理
    let arr=[]
    for(let i=0;i<1000000;i++){
        let item=Math.round(Math.random()*100);
        arr.push(item);
    }

    // 這行代碼會(huì)導(dǎo)致瀏覽器加載(下載)worker.js文件,但不會(huì)立即執(zhí)行
    let worker=new Worker('./worker.js')

    // 收到 worker 的返回結(jié)果
    worker.onmessage=function (event) {
        let data=event.data;
        console.log(data);
        worker.terminate();//終止worker的執(zhí)行
    }
    // worker內(nèi)部js執(zhí)行出錯(cuò)
    worker.onerror=function (event) {
        console.log(`Error ${event.filename } in line ${event.lineno}: ${event.message}`);
    }

    // 只有給worker傳遞消息后,它才會(huì)執(zhí)行相應(yīng)文件中的代碼
    // 而且數(shù)據(jù)會(huì)以異步方式被傳遞給worker
    worker.postMessage(arr);

在主頁面main.js代碼中,實(shí)例化 Worker 對(duì)象并傳入要執(zhí)行的 JS 文件名,這一對(duì)象可視為主線程中對(duì)新創(chuàng)建的工作線程的引用。然后調(diào)用worker.postMessage()方法,給新創(chuàng)建的工作線程傳遞消息,消息內(nèi)容可以是任何可被序列化的值(一般而言,能夠被序列化為 JSON 結(jié)構(gòu)的任何值都可作為參數(shù)被傳遞給 postMessage。而且傳入的值是復(fù)制到 worker中,而不是直接傳過去,即在 worker 中對(duì)傳入值的處理,不會(huì)影響主頁面中的原始值)。
worker 是通過messageerror與主頁面通信的,在主頁面中分別定義 onmessage事件和onerror事件的回調(diào)處理函數(shù),當(dāng)woker線程返回?cái)?shù)據(jù)時(shí),onmessage回調(diào)函數(shù)執(zhí)行,數(shù)據(jù)封裝在event參數(shù)的data屬性中。任何時(shí)候調(diào)用 worker 的 terminate()方法會(huì)立即終止worker中代碼執(zhí)行(后續(xù)過程不再發(fā)生,包括 message 和 error 事件也不會(huì)被觸發(fā));當(dāng)worker線程執(zhí)行出錯(cuò)時(shí)(只要 worker 內(nèi)部的 JS 在執(zhí)行過程中遇到錯(cuò)誤),onerror回調(diào)函數(shù)執(zhí)行,event參數(shù)中封裝了錯(cuò)誤對(duì)象的文件名、出錯(cuò)行號(hào)和具體錯(cuò)誤信息。

Dedicated Worker所執(zhí)行的代碼worker.js,例如是計(jì)算量較大的 js 處理。

self.onmessage=function (event) {
    let data=event.data.sort((v1,v2)=>v1-v2);
    self.postMessage(data);
    self.close();//停止工作
}

在worker.js代碼中,定義了onmessage事件處理函數(shù),由主線程傳入的數(shù)據(jù),封裝在event.data中,數(shù)據(jù)處理完成后,通過postMessage方法完成與主線程通信。在工作線程代碼中,onmessage事件和postMessage方法在其全局作用域可以訪問。在 Worker 內(nèi)部,可以調(diào)用 close() 方法停止工作,效果和在主頁面中調(diào)用 terminate() 方法一樣。

3. Worker全局作用域
Web Worker 所執(zhí)行的 JS 代碼完全在另一個(gè)作用域中,與主頁面中的代碼不共享作用域(在上文例子中,main.js 和 worker.js 中的代碼作用域互相獨(dú)立)。在 Web Worker中,同樣有一個(gè)全局對(duì)象以及其他對(duì)象和方法,但是 Web Worker 中的代碼不能訪問 dom,也無法影響頁面外觀。
Web Worker中的全局對(duì)象就是 worker 對(duì)象本身(注意,與主頁面 mian.js 中的 worker 不是同一個(gè)對(duì)象),即在這個(gè)特殊的全局作用域里,this 和 self 指向的都是 worker 對(duì)象。Web Worker 本身就是一個(gè)最小化的運(yùn)行環(huán)境。
在Web Worker中,可以獲得下列對(duì)象、方法
(1)navigator對(duì)象
(2)location對(duì)象,只讀
(3)XMLHttpRequest對(duì)象
(4)setTimeout/setInterval方法
(5)Application Cache
(6)通過importScripts()方法加載其他腳本
(7)創(chuàng)建新的Web Worker
但不能獲得下列對(duì)象
(1)DOM對(duì)象
(2)window對(duì)象
(3)document對(duì)象
(4)parent對(duì)象
上述的規(guī)范,限制了在worker線程中獲得主線程頁面相關(guān)對(duì)象的能力,所以在worker線程中,不能訪問dom元素,避免出現(xiàn)混亂的 dom 操作。

4. Worker線程執(zhí)行流程
webKit加載并執(zhí)行worker線程的流程如下圖所示

Worker線程執(zhí)行流程

解釋:
(1)worker線程的創(chuàng)建的是異步的
代碼執(zhí)行到"var worker = new Worker(task.js')“時(shí),在內(nèi)核中構(gòu)造WebCore::JSWorker對(duì)象(JSBbindings層)以及對(duì)應(yīng)的WebCore::Worker對(duì)象(WebCore模塊),根據(jù)初始化的url地址"task.js"發(fā)起異步加載的流程;主線程代碼不會(huì)阻塞在這里等待worker線程去加載、執(zhí)行指定的腳本文件,而是會(huì)立即向下繼續(xù)執(zhí)行后面代碼。
(2)postMessage消息交互由內(nèi)核調(diào)度
main.js中,在創(chuàng)建woker線程后,立即調(diào)用了postMessage方法傳遞了數(shù)據(jù),在worker線程還沒創(chuàng)建完成時(shí),main.js中發(fā)出的消息,會(huì)先存儲(chǔ)在一個(gè)臨時(shí)消息隊(duì)列中,當(dāng)異步創(chuàng)建worker線程完成,臨時(shí)消息隊(duì)列中的消息數(shù)據(jù)復(fù)制到woker對(duì)應(yīng)的WorkerRunLoop的消息隊(duì)列中,worker線程開始處理消息。在經(jīng)過一輪消息來回后,繼續(xù)通信時(shí), 這個(gè)時(shí)候因?yàn)閣orker線程已經(jīng)創(chuàng)建,所以消息會(huì)直接添加到WorkerRunLoop的消息隊(duì)列中;

5. Worker線程數(shù)據(jù)通訊方式
主線程與子線程數(shù)據(jù)通信方式有多種,通信內(nèi)容,可以是文本,也可以是對(duì)象。需要注意的是,這種通信是拷貝關(guān)系,即是傳值而不是地址,子線程對(duì)通信內(nèi)容的修改,不會(huì)影響到主線程。事實(shí)上,瀏覽器內(nèi)部的運(yùn)行機(jī)制是,先將通信內(nèi)容串行化,然后把串行化后的字符串發(fā)給子線程,后者再將它還原。
主線程與子線程之間也可以交換二進(jìn)制數(shù)據(jù),比如File、Blob、ArrayBuffer等對(duì)象,也可以在線程之間發(fā)送。但是,用拷貝方式發(fā)送二進(jìn)制數(shù)據(jù),會(huì)造成性能問題。比如,主線程向子線程發(fā)送一個(gè)50MB文件,默認(rèn)情況下瀏覽器會(huì)生成一個(gè)原文件的拷貝。為了解決這個(gè)問題,JavaScript允許主線程把二進(jìn)制數(shù)據(jù)直接轉(zhuǎn)移給子線程,轉(zhuǎn)移后主線程無法再使用這些數(shù)據(jù),這是為了防止出現(xiàn)多個(gè)線程同時(shí)修改數(shù)據(jù)的問題,這種轉(zhuǎn)移數(shù)據(jù)的方法,叫做Transferable Objects。

// Create a 32MB "file" and fill it.
let uInt8Array = new Uint8Array(1024*1024*32); // 32MB
for (let i = 0,len=uInt8Array .length ; i <len ; ++i) {
    uInt8Array[i] = i;
}
worker.postMessage(uInt8Array.buffer, [uInt8Array.buffer]);

6. Web Worker作用
Web Worker并沒有為Javascript帶來多線程編程能力,JS 代碼的執(zhí)行仍然是單線程的。Web Worker帶來的是后臺(tái)計(jì)算能力。
Web Worker自身是由webkit多線程實(shí)現(xiàn),但它并沒有為Javasctipt語言帶來多線程編程特性,我們現(xiàn)在仍然不能在Javascript代碼中創(chuàng)建并管理一個(gè)線程,或者主動(dòng)控制線程間的同步與鎖等特性。
在我看來,Web Worker可以說是worker編程模型在瀏覽器端Javascript語言中的應(yīng)用。瀏覽器的運(yùn)行時(shí),同其他GUI程序類似,核心邏輯像是下面這個(gè)無限循環(huán):

while(true){  
    1 處理數(shù)據(jù)和更新對(duì)象狀態(tài)  
    2 渲染可視化UI  
}

在Web Worker之前,Javascript執(zhí)行引擎只能在一個(gè)單線程環(huán)境中完成這兩項(xiàng)任務(wù)。Web Worker的引入,是借鑒了worker編程模型,給單線程的Javascript帶來了后臺(tái)計(jì)算的能力。
既然Web Worker為瀏覽器端Javascript帶來了后臺(tái)計(jì)算能力,我們便可利用這一能力,將無限循環(huán)中第一項(xiàng)“處理數(shù)據(jù)和更新對(duì)象狀態(tài) ”的耗時(shí)部分交由Web Worker執(zhí)行,提升頁面性能。
部分典型的應(yīng)用場(chǎng)景如下:
(1)使用專用線程進(jìn)行數(shù)學(xué)運(yùn)算
Web Worker最簡(jiǎn)單的應(yīng)用就是用來做后臺(tái)計(jì)算,而這種計(jì)算并不會(huì)中斷主頁面的代碼執(zhí)行。
(2)圖像處理
通過使用從<canvas>或者<video>元素中獲取的數(shù)據(jù),可以把圖像分割成幾個(gè)不同的區(qū)域并且把它們推送給并行的不同Workers來做計(jì)算(如將彩色圖像轉(zhuǎn)換成灰階圖像)。
(3)大量數(shù)據(jù)的檢索
當(dāng)需要在調(diào)用 ajax后處理大量的數(shù)據(jù),如果處理這些數(shù)據(jù)所需的時(shí)間長(zhǎng)短非常重要,可以在Web Worker中來做這些,避免凍結(jié)UI線程。

reference
1.The Basics of Web Workers
http://www.html5rocks.com/en/tutorials/workers/basics/

  1. 深入 HTML5 Web Worker 應(yīng)用實(shí)踐:多線程編程
    http://www.ibm.com/developerworks/cn/web/1112_sunch_webworker/index.html
  2. JavaScript 工作線程實(shí)現(xiàn)方式
    http://www.ibm.com/developerworks/cn/web/1105_chengfu_jsworker/index.html

4.HTML5 與 ”性工能“障礙
http://fins.iteye.com/blog/1747321

5.Web Worker在WebKit中的實(shí)現(xiàn)機(jī)制
http://blog.csdn.net/codigger/article/details/40581343

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

友情鏈接更多精彩內(nèi)容