Javascript運(yùn)行在單線程環(huán)境中款咖,雖然單線程避免了多線程的難點(diǎn),但缺點(diǎn)也很明顯奄喂,如果某個任務(wù)耗時很久铐殃,就無法處理其他任務(wù),會讓用戶感覺瀏覽器卡住了跨新。所以瀏覽器中除了JS引擎線程富腊,還有其他線程,比如 GUI 渲染線程域帐、http 請求線程赘被、事件觸發(fā)線程等,而通過事件循環(huán)機(jī)制對setTimeout/setInterval肖揣、ajax和dom事件的異步處理民假,就需要多個線程的參與。
HTML5引進(jìn)了 Web Worker龙优,可讓JS在后臺運(yùn)行羊异,執(zhí)行耗時長的任務(wù),而不影響主頁面代碼的執(zhí)行。
1.定義:Web Worker 是HTML5標(biāo)準(zhǔn)的一部分球化,允許一段JavaScript程序運(yùn)行在主線程之外的另外一個線程中秽晚。Web Worker 規(guī)范中定義了兩類工作線程,分別是專用線程Dedicated Worker和共享線程 Shared Worker筒愚,其中赴蝇,Dedicated Worker只能為一個頁面所使用,而Shared Worker可以被多個頁面所共享巢掺,本文以前者為例句伶。
2.示例:
主頁面代碼 main.js
// 一個較大的數(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);
}
// 這行代碼會導(dǎo)致瀏覽器加載(下載)worker.js文件考余,但不會立即執(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í)行出錯
worker.onerror=function (event) {
console.log(`Error ${event.filename } in line ${event.lineno}: ${event.message}`);
}
// 只有給worker傳遞消息后,它才會執(zhí)行相應(yīng)文件中的代碼
// 而且數(shù)據(jù)會以異步方式被傳遞給worker
worker.postMessage(arr);
在主頁面main.js代碼中轧苫,實(shí)例化 Worker 對象并傳入要執(zhí)行的 JS 文件名楚堤,這一對象可視為主線程中對新創(chuàng)建的工作線程的引用。然后調(diào)用worker.postMessage()方法含懊,給新創(chuàng)建的工作線程傳遞消息身冬,消息內(nèi)容可以是任何可被序列化的值(一般而言,能夠被序列化為 JSON 結(jié)構(gòu)的任何值都可作為參數(shù)被傳遞給 postMessage岔乔。而且傳入的值是復(fù)制到 worker中酥筝,而不是直接傳過去,即在 worker 中對傳入值的處理雏门,不會影響主頁面中的原始值)嘿歌。
worker 是通過message
和error
與主頁面通信的,在主頁面中分別定義 onmessage
事件和onerror
事件的回調(diào)處理函數(shù)茁影,當(dāng)woker線程返回數(shù)據(jù)時宙帝,onmessage回調(diào)函數(shù)執(zhí)行,數(shù)據(jù)封裝在event參數(shù)的data屬性中募闲。任何時候調(diào)用 worker 的 terminate()方法會立即終止worker中代碼執(zhí)行(后續(xù)過程不再發(fā)生茄唐,包括 message 和 error 事件也不會被觸發(fā));當(dāng)worker線程執(zhí)行出錯時(只要 worker 內(nèi)部的 JS 在執(zhí)行過程中遇到錯誤)蝇更,onerror回調(diào)函數(shù)執(zhí)行,event參數(shù)中封裝了錯誤對象的文件名呼盆、出錯行號和具體錯誤信息年扩。
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 代碼完全在另一個作用域中,與主頁面中的代碼不共享作用域(在上文例子中悄但,main.js 和 worker.js 中的代碼作用域互相獨(dú)立)棠隐。在 Web Worker中,同樣有一個全局對象以及其他對象和方法檐嚣,但是 Web Worker 中的代碼不能訪問 dom助泽,也無法影響頁面外觀。
Web Worker中的全局對象就是 worker 對象本身(注意嚎京,與主頁面 mian.js 中的 worker 不是同一個對象)嗡贺,即在這個特殊的全局作用域里,this 和 self 指向的都是 worker 對象挖藏。Web Worker 本身就是一個最小化的運(yùn)行環(huán)境暑刃。
在Web Worker中,可以獲得下列對象膜眠、方法
(1)navigator對象
(2)location對象岩臣,只讀
(3)XMLHttpRequest對象
(4)setTimeout/setInterval方法
(5)Application Cache
(6)通過importScripts()方法加載其他腳本
(7)創(chuàng)建新的Web Worker
但不能獲得下列對象
(1)DOM對象
(2)window對象
(3)document對象
(4)parent對象
上述的規(guī)范,限制了在worker線程中獲得主線程頁面相關(guān)對象的能力宵膨,所以在worker線程中架谎,不能訪問dom元素,避免出現(xiàn)混亂的 dom 操作辟躏。
4. Worker線程執(zhí)行流程
webKit加載并執(zhí)行worker線程的流程如下圖所示
解釋:
(1)worker線程的創(chuàng)建的是異步的
代碼執(zhí)行到"var worker = new Worker(task.js')“時谷扣,在內(nèi)核中構(gòu)造WebCore::JSWorker對象(JSBbindings層)以及對應(yīng)的WebCore::Worker對象(WebCore模塊),根據(jù)初始化的url地址"task.js"發(fā)起異步加載的流程捎琐;主線程代碼不會阻塞在這里等待worker線程去加載会涎、執(zhí)行指定的腳本文件,而是會立即向下繼續(xù)執(zhí)行后面代碼瑞凑。
(2)postMessage消息交互由內(nèi)核調(diào)度
main.js中末秃,在創(chuàng)建woker線程后,立即調(diào)用了postMessage方法傳遞了數(shù)據(jù)籽御,在worker線程還沒創(chuàng)建完成時蹋岩,main.js中發(fā)出的消息,會先存儲在一個臨時消息隊(duì)列中铛铁,當(dāng)異步創(chuàng)建worker線程完成,臨時消息隊(duì)列中的消息數(shù)據(jù)復(fù)制到woker對應(yīng)的WorkerRunLoop的消息隊(duì)列中项鬼,worker線程開始處理消息。在經(jīng)過一輪消息來回后劲阎,繼續(xù)通信時绘盟, 這個時候因?yàn)閣orker線程已經(jīng)創(chuàng)建,所以消息會直接添加到WorkerRunLoop的消息隊(duì)列中哪工;
5. Worker線程數(shù)據(jù)通訊方式
主線程與子線程數(shù)據(jù)通信方式有多種奥此,通信內(nèi)容,可以是文本雁比,也可以是對象稚虎。需要注意的是,這種通信是拷貝關(guān)系偎捎,即是傳值而不是地址蠢终,子線程對通信內(nèi)容的修改,不會影響到主線程茴她。事實(shí)上寻拂,瀏覽器內(nèi)部的運(yùn)行機(jī)制是,先將通信內(nèi)容串行化丈牢,然后把串行化后的字符串發(fā)給子線程祭钉,后者再將它還原。
主線程與子線程之間也可以交換二進(jìn)制數(shù)據(jù)己沛,比如File慌核、Blob、ArrayBuffer等對象申尼,也可以在線程之間發(fā)送垮卓。但是,用拷貝方式發(fā)送二進(jìn)制數(shù)據(jù)师幕,會造成性能問題粟按。比如,主線程向子線程發(fā)送一個50MB文件霹粥,默認(rèn)情況下瀏覽器會生成一個原文件的拷貝灭将。為了解決這個問題,JavaScript允許主線程把二進(jìn)制數(shù)據(jù)直接轉(zhuǎn)移給子線程后控,轉(zhuǎn)移后主線程無法再使用這些數(shù)據(jù)宗侦,這是為了防止出現(xiàn)多個線程同時修改數(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帶來的是后臺計(jì)算能力男旗。
Web Worker自身是由webkit多線程實(shí)現(xiàn),但它并沒有為Javasctipt語言帶來多線程編程特性欣鳖,我們現(xiàn)在仍然不能在Javascript代碼中創(chuàng)建并管理一個線程察皇,或者主動控制線程間的同步與鎖等特性。
在我看來泽台,Web Worker可以說是worker編程模型在瀏覽器端Javascript語言中的應(yīng)用什荣。瀏覽器的運(yùn)行時,同其他GUI程序類似,核心邏輯像是下面這個無限循環(huán):
while(true){
1 處理數(shù)據(jù)和更新對象狀態(tài)
2 渲染可視化UI
}
在Web Worker之前怀酷,Javascript執(zhí)行引擎只能在一個單線程環(huán)境中完成這兩項(xiàng)任務(wù)稻爬。Web Worker的引入,是借鑒了worker編程模型蜕依,給單線程的Javascript帶來了后臺計(jì)算的能力桅锄。
既然Web Worker為瀏覽器端Javascript帶來了后臺計(jì)算能力,我們便可利用這一能力样眠,將無限循環(huán)中第一項(xiàng)“處理數(shù)據(jù)和更新對象狀態(tài) ”的耗時部分交由Web Worker執(zhí)行友瘤,提升頁面性能。
部分典型的應(yīng)用場景如下:
(1)使用專用線程進(jìn)行數(shù)學(xué)運(yùn)算
Web Worker最簡單的應(yīng)用就是用來做后臺計(jì)算檐束,而這種計(jì)算并不會中斷主頁面的代碼執(zhí)行辫秧。
(2)圖像處理
通過使用從<canvas>或者<video>元素中獲取的數(shù)據(jù),可以把圖像分割成幾個不同的區(qū)域并且把它們推送給并行的不同Workers來做計(jì)算(如將彩色圖像轉(zhuǎn)換成灰階圖像)被丧。
(3)大量數(shù)據(jù)的檢索
當(dāng)需要在調(diào)用 ajax后處理大量的數(shù)據(jù)盟戏,如果處理這些數(shù)據(jù)所需的時間長短非常重要,可以在Web Worker中來做這些晚碾,避免凍結(jié)UI線程抓半。
reference
1.The Basics of Web Workers
http://www.html5rocks.com/en/tutorials/workers/basics/
- 深入 HTML5 Web Worker 應(yīng)用實(shí)踐:多線程編程
http://www.ibm.com/developerworks/cn/web/1112_sunch_webworker/index.html - 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