摘要:不過(guò),這并不意味著語(yǔ)言本身就支持了多線程,對(duì)于語(yǔ)言本身它仍是運(yùn)行在單線程上的,只是瀏覽器宿主環(huán)境提供的一個(gè)能力。主線程與子線程之間也可以交換二進(jìn)制數(shù)據(jù),比如等對(duì)象,也可以在線程之間發(fā)送。
先看幾個(gè)例子
本例子是通過(guò)通過(guò)紅點(diǎn)展示地球上的地震帶,數(shù)據(jù)來(lái)自于地質(zhì)探測(cè)局
通過(guò)console.log看到數(shù)據(jù)運(yùn)算所耗的時(shí)間
不使用 webworker No web workers - all on main thread
使用一條 webworker One web worker
使用兩條 Two web workers
使用八條 Eight web workers
使用20條 20 web workers
結(jié)論:是? // 帶著思考看下去
背景JavaScript引擎是單線程運(yùn)行的,JavaScript中耗時(shí)的I/O操作都被處理為異步操作,它們包括鍵盤(pán)、鼠標(biāo)I/O輸入輸出事件、窗口大小的resize事件、定時(shí)器(setTimeout、setInterval)事件、Ajax請(qǐng)求網(wǎng)絡(luò)I/O回調(diào)等。當(dāng)這些異步任務(wù)發(fā)生的時(shí)候,它們將會(huì)被放入瀏覽器的事件任務(wù)隊(duì)列中去,等到JavaScript運(yùn)行時(shí)執(zhí)行線程空閑時(shí)候才會(huì)按照隊(duì)列先進(jìn)先出的原則被一一執(zhí)行,但終究還是單線程。
雖然JS運(yùn)行在瀏覽器中,是單線程的,每個(gè)window一個(gè)JS線程,但瀏覽器不是單線程的,例如Webkit或是Gecko引擎,都可能有如下線程:
javascript引擎線程 界面渲染線程 瀏覽器事件觸發(fā)線程 Http請(qǐng)求線程
很多人覺(jué)得異步(promise async/await),都是通過(guò)類(lèi)似event loop在平常的工作中已經(jīng)足夠,但是如果做復(fù)雜運(yùn)算,這些異步偽線程的不足就逐漸體現(xiàn)出來(lái),比如settimeout拿到的值并不正確,再者假如頁(yè)面有復(fù)雜運(yùn)算的時(shí)候頁(yè)面很容易觸發(fā)假死狀態(tài),
為了有多線程功能,webworker問(wèn)世了。不過(guò),這并不意味著 JavaScript 語(yǔ)言本身就支持了多線程,對(duì)于 JavaScript 語(yǔ)言本身它仍是運(yùn)行在單線程上的, Web Worker 只是瀏覽器(宿主環(huán)境)提供的一個(gè)能力/API。
Web Worker 是HTML5標(biāo)準(zhǔn)的一部分,這一規(guī)范定義了一套 API,它允許一段JavaScript程序運(yùn)行在主線程之外的另外一個(gè)線程中。工作線程允許開(kāi)發(fā)人員編寫(xiě)能夠長(zhǎng)時(shí)間運(yùn)行而不被用戶所中斷的后臺(tái)程序, 去執(zhí)行事務(wù)或者邏輯,并同時(shí)保證頁(yè)面對(duì)用戶的及時(shí)響應(yīng),可以將一些大量計(jì)算的代碼交給web worker運(yùn)行而不凍結(jié)用戶界面,后面會(huì)有案例介紹
類(lèi)型Web workers可分為兩種類(lèi)型:專(zhuān)用線程dedicated web worker,以及共享線程shared web worker。 Dedicated web worker隨當(dāng)前頁(yè)面的關(guān)閉而結(jié)束;這意味著Dedicated web worker只能被創(chuàng)建它的頁(yè)面訪問(wèn)。與之相對(duì)應(yīng)的Shared web worker可以被多個(gè)頁(yè)面訪問(wèn)。在Javascript代碼中,“Work”類(lèi)型代表Dedicated web worker,而“SharedWorker”類(lèi)型代表Shared web worker。
而Shared Worker則可以被多個(gè)頁(yè)面所共享(同域情況下)
Web Worker的創(chuàng)建是在主線程當(dāng)中通過(guò)傳入文件的url來(lái)實(shí)現(xiàn)的。如下所示:
let webworker = new Worker("myworker.js");
返回的是webworker實(shí)例對(duì)象,該對(duì)象是主線程和其他線程的通訊橋梁
主線程和其他線程可以通過(guò)
onmessage: 監(jiān)聽(tīng)事件 postmessage: 傳送事件
相關(guān)的API進(jìn)行通訊
案例代碼如下
//主線程 main.js var worker = new Worker("worker.js"); worker.onmessage = function(event){ // 主線程收到子線程的消息 }; // 主線程向子線程發(fā)送消息 worker.postMessage({ type: "start", value: 12345 }); //web worker.js onmessage = function(event){ // 收到 }; postMessage({ type: "debug", message: "Starting processing..." });
相關(guān)demo
如何終止如果在某個(gè)時(shí)機(jī)不想要 Worker 繼續(xù)運(yùn)行了,那么我們需要終止掉這個(gè)線程,可以調(diào)用 在主線程worker 的 terminate 方法 或者在相應(yīng)的線程中調(diào)用close:
// 方式一 main.js 在主線程停止方式 var worker = new Worker("./worker.js"); ... worker.terminate(); // 方式二、worker.js self.close()錯(cuò)誤機(jī)制
提供了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: 導(dǎo)致錯(cuò)誤的 Worker 腳本的名稱; // event.message: 錯(cuò)誤的信息; // event.lineno: 出現(xiàn)錯(cuò)誤的行號(hào);sharedWorker
對(duì)于 Web Worker ,一個(gè) tab 頁(yè)面只能對(duì)應(yīng)一個(gè) Worker 線程,是相互獨(dú)立的;
而 SharedWorker 提供了能力能夠讓不同標(biāo)簽中頁(yè)面共享的同一個(gè) Worker 腳本線程;
當(dāng)然,有個(gè)很重要的限制就是它們需要滿足同源策略,也就是需要在同域下;
在頁(yè)面(可以多個(gè))中實(shí)例化 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
線程中再創(chuàng)建線程
環(huán)境與作用域在 Worker 線程的運(yùn)行環(huán)境中沒(méi)有 window 全局對(duì)象,也無(wú)法訪問(wèn) DOM 對(duì)象,所以一般來(lái)說(shuō)他只能來(lái)執(zhí)行純 JavaScript 的計(jì)算操作。但是,他還是可以獲取到部分瀏覽器提供的 API 的:
setTimeout(), clearTimeout(), setInterval(), clearInterval():有了設(shè)計(jì)個(gè)函數(shù),就可以在 Worker : 線程中可以再創(chuàng)建worker;
XMLHttpRequest : 對(duì)象:意味著我們可以在 Worker 線程中執(zhí)行 ajax 請(qǐng)求;
navigator 對(duì)象:可以獲取到 ppName,appVersion,platform,userAgent 等信息;
location 對(duì)象(只讀):可以獲取到有關(guān)當(dāng)前 URL 的信息;
Application Cache
indexedDB
WebSocket、
Promise、
在線程中,提供了importScripts方法
如果線程中使用了importScripts 一般按照以下步驟解析
1、解析 importScripts方法的每一個(gè)參數(shù)。 2、如果有任何失敗或者錯(cuò)誤,拋出 SYNTAX_ERR 異常。 3、嘗試從用戶提供的 URL 資源位置處獲取腳本資源。 4、對(duì)于 importScripts 方法的每一個(gè)參數(shù),按照用戶的提供順序,獲取腳本資源后繼續(xù)進(jìn)行其它操作。
// 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中的方法 };
也可以一次性引入多個(gè)
//可以多起一次傳入 importScripts("script1.js", "script2.js");XMLHttpRequest
onmessage = function(evt){ var xhr = new XMLHttpRequest(); xhr.open("GET", "serviceUrl"); //serviceUrl為后端j返回son數(shù)據(jù)的接口 xhr.onload = function(){ postMessage(xhr.responseText); }; xhr.send(); }
// 設(shè)置jsonp function MakeServerRequest() { importScripts("http://SomeServer.com?jsonp=HandleRequest"); } // jsonp回調(diào) function HandleRequest(objJSON) { postMessage("Data returned from the server...FirstName: " + objJSON.FirstName + " LastName: " + objJSON.LastName); } // Trigger the server request for the JSONP data MakeServerRequest();通訊原理
從一個(gè)線程到另一個(gè)線程的通訊實(shí)際上是一個(gè)值拷貝的過(guò)程,實(shí)際上是先將數(shù)據(jù)JSON.stringify之后再JSON.parse。主線程與子線程之間也可以交換二進(jìn)制數(shù)據(jù),比如File、Blob、ArrayBuffer等對(duì)象,也可以在線程之間發(fā)送。但是,用拷貝方式發(fā)送二進(jìn)制數(shù)據(jù),會(huì)造成性能問(wèn)題。比如,主線程向子線程發(fā)送一個(gè)50MB文件,默認(rèn)情況下瀏覽器會(huì)生成一個(gè)原文件的拷貝。為了解決這個(gè)問(wèn)題,JavaScript允許主線程把二進(jìn)制數(shù)據(jù)直接轉(zhuǎn)移給子線程,轉(zhuǎn)移后主線程無(wú)法再使用這些數(shù)據(jù),這是為了防止出現(xiàn)多個(gè)線程同時(shí)修改數(shù)據(jù)的問(wèn)題,這種轉(zhuǎn)移數(shù)據(jù)的方法,叫做Transferable Objects。
不過(guò)現(xiàn)在很多瀏覽器支持transferable objects(可轉(zhuǎn)讓對(duì)象) ,這個(gè)技術(shù)是零拷貝轉(zhuǎn)移,能大大提升性能,
可以指定傳送的數(shù)據(jù)全都是零拷貝
var abBuffer = new ArrayBuffer(32); aDedicatedWorker.postMessage(abBuffer, [abBuffer]);
也可以 指定某個(gè)是 使用 零拷貝
var objData = { "employeeId": 103, "name": "Sam Smith", "dateHired": new Date(2006, 11, 15), "abBuffer": new ArrayBuffer(32) }; aDedicatedWorker.postMessage(objData, [objData.abBuffer]);工作線程生命周期
工作線程之間的通信必須依賴于瀏覽器的上下文環(huán)境,并且通過(guò)它們的 MessagePort 對(duì)象實(shí)例傳遞消息。每個(gè)工作線程的全局作用域都擁有這些線程的端口列表,這些列表包括了所有線程使用到的 MessagePort 對(duì)象。在專(zhuān)用線程的情況下,這個(gè)列表還會(huì)包含隱式的 MessagePort 對(duì)象。
每個(gè)工作線程的全局作用域?qū)ο?WorkerGlobalScope 還會(huì)有一個(gè)工作線程的線程列表,在初始化時(shí)這個(gè)列表為空。當(dāng)工作線程被創(chuàng)建的時(shí)候或者擁有父工作線程的時(shí)候,它們就會(huì)被填充進(jìn)來(lái)。
最后,每個(gè)工作線程的全局作用域?qū)ο?b> WorkerGlobalScope 還擁有這個(gè)線程的文檔模型,在初始化時(shí)這個(gè)列表為空。當(dāng)工作線程被創(chuàng)建的時(shí)候,文檔對(duì)象就會(huì)被填充進(jìn)來(lái)。無(wú)論何時(shí)當(dāng)一個(gè)文檔對(duì)象被丟棄的時(shí)候,它就要從這個(gè)文檔對(duì)象列舉里面刪除出來(lái)。
// 部分機(jī)器webwoker初始化時(shí)間 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)
1、普通json/object
2、tranferable objects
可見(jiàn) transferable objects傳輸速度要高很多
1) 使用專(zhuān)用線程進(jìn)行數(shù)學(xué)運(yùn)算
Web Worker最簡(jiǎn)單的應(yīng)用就是用來(lái)做后臺(tái)計(jì)算,而這種計(jì)算并不會(huì)中斷前臺(tái)用戶的操作
2) 圖像處理
通過(guò)使用從或者元素中獲取的數(shù)據(jù),可以把圖像分割成幾個(gè)不同的區(qū)域并且把它們推送給并行的不同Workers來(lái)做計(jì)算
3) 大量數(shù)據(jù)的檢索
當(dāng)需要在調(diào)用 ajax后處理大量的數(shù)據(jù),如果處理這些數(shù)據(jù)所需的時(shí)間長(zhǎng)短非常重要,可以在Web Worker中來(lái)做這些,避免凍結(jié)UI線程。
4) 背景數(shù)據(jù)分析
由于在使用Web Worker的時(shí)候,我們有更多潛在的CPU可用時(shí)間,我們現(xiàn)在可以考慮一下JavaScript中的新應(yīng)用場(chǎng)景。例如,我們可以想像在不影響UI體驗(yàn)的情況下實(shí)時(shí)處理用戶輸入。利用這樣一種可能,我們可以想像一個(gè)像Word(Office Web Apps 套裝)一樣的應(yīng)用:當(dāng)用戶打字時(shí)后臺(tái)在詞典中進(jìn)行查找,幫助用戶自動(dòng)糾錯(cuò)等等。
1、不能訪問(wèn)DOM和BOM對(duì)象的,Location和navigator的只讀訪問(wèn),并且navigator封裝成了WorkerNavigator對(duì)象,更改部分屬性。無(wú)法讀取本地文件系統(tǒng)
2、子線程和父級(jí)線程的通訊是通過(guò)值拷貝,子線程對(duì)通信內(nèi)容的修改,不會(huì)影響到主線程。在通訊過(guò)程中值過(guò)大也會(huì)影響到性能(解決這個(gè)問(wèn)題可以用transferable objects)
3、并非真的多線程,多線程是因?yàn)闉g覽器的功能
4、兼容性
5 因?yàn)榫€程是通過(guò)importScripts引入外部的js,并且直接執(zhí)行,其實(shí)是不安全的,很容易被外部注入一些惡意代碼
6、條數(shù)限制,大多瀏覽器能創(chuàng)建webworker線程的條數(shù)是有限制的,雖然可以手動(dòng)去拓展,但是如果不設(shè)置的話,基本上都在20條以內(nèi),每條線程大概5M左右,需要手動(dòng)關(guān)掉一些不用的線程才能夠創(chuàng)建新的線程(相關(guān)解決方案)
7、js存在真的線程的東西,比如SharedArrayBuffer
1、tagg2
參考文獻(xiàn):[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...
文章版權(quán)歸作者所有,未經(jīng)允許請(qǐng)勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。
轉(zhuǎn)載請(qǐng)注明本文地址:http://www.ezyhdfw.cn/yun/91691.html
摘要:淺談前言都知道是單線程語(yǔ)言,最讓人頭疼的莫過(guò)于在網(wǎng)絡(luò)正常的情況下經(jīng)常出現(xiàn)頁(yè)面的假死,以及在進(jìn)行大量的循環(huán)計(jì)算時(shí)會(huì)導(dǎo)致線程阻塞由于要進(jìn)行大量的計(jì)算后面的運(yùn)行會(huì)被阻隔在此處,使得性能較差,代碼維護(hù)性差等一系列的問(wèn)題發(fā)生。 WebWork淺談 前言: 都知道JS是單線程語(yǔ)言,最讓人頭疼的莫過(guò)于在網(wǎng)絡(luò)正常的情況下經(jīng)常出現(xiàn)頁(yè)面的假死, 以及在進(jìn)行大量的for循環(huán)計(jì)算時(shí)會(huì)導(dǎo)致線程阻塞,由于要進(jìn)行...
摘要:淺談前言都知道是單線程語(yǔ)言,最讓人頭疼的莫過(guò)于在網(wǎng)絡(luò)正常的情況下經(jīng)常出現(xiàn)頁(yè)面的假死,以及在進(jìn)行大量的循環(huán)計(jì)算時(shí)會(huì)導(dǎo)致線程阻塞由于要進(jìn)行大量的計(jì)算后面的運(yùn)行會(huì)被阻隔在此處,使得性能較差,代碼維護(hù)性差等一系列的問(wèn)題發(fā)生。 WebWork淺談 前言: 都知道JS是單線程語(yǔ)言,最讓人頭疼的莫過(guò)于在網(wǎng)絡(luò)正常的情況下經(jīng)常出現(xiàn)頁(yè)面的假死, 以及在進(jìn)行大量的for循環(huán)計(jì)算時(shí)會(huì)導(dǎo)致線程阻塞,由于要進(jìn)行...
摘要:淺談前言都知道是單線程語(yǔ)言,最讓人頭疼的莫過(guò)于在網(wǎng)絡(luò)正常的情況下經(jīng)常出現(xiàn)頁(yè)面的假死,以及在進(jìn)行大量的循環(huán)計(jì)算時(shí)會(huì)導(dǎo)致線程阻塞由于要進(jìn)行大量的計(jì)算后面的運(yùn)行會(huì)被阻隔在此處,使得性能較差,代碼維護(hù)性差等一系列的問(wèn)題發(fā)生。 WebWork淺談 前言: 都知道JS是單線程語(yǔ)言,最讓人頭疼的莫過(guò)于在網(wǎng)絡(luò)正常的情況下經(jīng)常出現(xiàn)頁(yè)面的假死, 以及在進(jìn)行大量的for循環(huán)計(jì)算時(shí)會(huì)導(dǎo)致線程阻塞,由于要進(jìn)行...
閱讀 3389·2021-11-24 09:39
閱讀 3165·2021-11-23 09:51
閱讀 971·2021-11-18 10:07
閱讀 3610·2021-10-11 10:57
閱讀 2846·2021-10-08 10:04
閱讀 3080·2021-09-26 10:11
閱讀 1160·2021-09-23 11:21
閱讀 2984·2019-08-29 17:28