開始使用Web Workers
單執行緒(Single-threaded)執行是JavaScript語言的設計目標之一,進而言之是保持JavaScript的簡單。但是我必須要說,儘管JavaScript具有如此語言特質,但它絕不簡單!我們所說的“單執行緒”是指JavaScript只有一個執行緒控制。是的,這點令人沮喪,JavaScript引擎一次只能做一件事。
“web workers處在一個嚴格的無DOM訪問的環境裡,因為DOM是非執行緒安全的。”
現在,你是不是覺得要想利用下你機器閒置的多核處理器太受限制?不用擔心,HTML5將改變這一切。
JavaScript的單執行緒模式
有學派認為JavaScript的單執行緒特質是一種簡化,然而也有人認為這是一種限制。後者提出的是一個很好的觀點,尤其是現在web應用程式大量的使用JavaScript來處理介面事件、輪詢服務端介面、處理大量的資料以及基於服務端的響應操作DOM。
在維護響應式介面的同時,通過單執行緒控制處理如此多事件是項艱鉅的任務。它迫使開發人員不得不依靠一些技巧或採用變通的方法(如使用setTimeout(),setInterval(),或呼叫XMLHttpRequest和DOM事件)來實現併發。然而,儘管這些技巧毫無疑問地提供瞭解決非同步呼叫的方法,但非阻塞的並不意味著是併發的。John Resig在他的部落格中解釋了為什麼不能並行執行。
限制
如果你已經和JavaScript打過一段時間的交道,那麼你一定也遭遇過如下令人討厭的對話方塊,提示你有指令碼無響應。沒錯,幾乎大多數的頁面無響應都是由JavaScript程式碼引起的。
以下是一些執行指令碼時造成瀏覽器無響應的原因:
- 過多的DOM操作:DOM操作也許是在JavaScript執行中代價最高的。所以,大批量的DOM操作無疑是你程式碼重構的最佳方向之一。
- 無終止迴圈:審視你程式碼中複雜的巢狀迴圈永遠不是壞事。複雜的巢狀迴圈所做的工作常常比實際需要做的多很多,也許你可以找到其他方法來實現同樣的功能。
- 同時包含以上兩種:最壞的情況就是明明有更優雅的辦法,卻還是在迴圈中不斷更新DOM元素,比如可以採用DocumentFragment。
好幫手Web Workers
幸好有了HTML5和Web Workers,你可以真正生成一條非同步的執行緒。當主執行緒處理介面事件時,新的worker可以在後臺執行,它甚至可以有力的處理大量的資料。例如,一個worker可以處理大型的資料結構(如JSON),從中提取變數資訊然後在介面中顯示。好了,廢話不多說,讓我們看一些實際的程式碼吧。
建立一個Worker
通常,與web worker相關的程式碼都放在一個獨立的JavaScript檔案中。父執行緒通過在Worker建構函式中指定一個JavaScript檔案的連結來建立一個新的worker,它會非同步載入並執行這個JavaScript檔案。
var primeWorker = new Worker('prime.js');
啟動Worker
要啟動一個Worker,則父執行緒向worker傳遞一個資訊,如下所示:
var current = $('#prime').attr('value'); primeWorker.postMessage(current);
父頁面可以通過postMessage介面與worker進行通訊,這也是跨源通訊(cross-origin messaging)的一種方式。通過postMessage介面除了可以向worker傳遞私有資料型別,它還支援JSON資料結構。但是,你不能傳遞函式,因為函式也許會包含對潛在DOM的引用。
“父執行緒和worker執行緒有它們各自的獨立空間,資訊主要是來回交換而不是共享。”
資訊在後臺執行時,先在worker端序列化,然後在接收端反序列化。鑑於此,不推薦向worker傳送大量的資料。
父執行緒同樣可以宣告一個回撥函式,來偵聽worker完成任務後發回的訊息。這樣,父執行緒就可以在worker完成任務後採取些必要的行動,比如更新DOM元素。如下程式碼所示:
primeWorker.addEventListener('message', function(event){ console.log('Receiving from Worker: '+event.data); $('#prime').html( event.data ); });
event物件包含兩個重要屬性:
- target:用來指向傳送資訊的worker,在多元worker環境下比較有用。
- data:由worker發回給父執行緒的資料。
worker本身是包含在prime.js檔案中的,它同時偵聽message事件,從父執行緒中接收資訊。它同樣通過postMessage介面與父執行緒進行通訊。
self.addEventListener('message', function(event){ var currPrime = event.data, nextPrime; setInterval( function(){ nextPrime = getNextPrime(currPrime); postMessage(nextPrime); currPrime = nextPrime; }, 500); });
在本文例子中,我們尋找下一個最大的質數,然後不斷將結果發回至父執行緒,同時不斷更新介面以顯示新的值。在worker的程式碼中,欄位self和this都是指向全域性作用域。Worker既可以新增事件偵聽器來偵聽message事件,也可以定義一個onmessage處理器,來接收從父執行緒發回的訊息。
尋找下一個質數的例子顯然不是worker的理想用例,但是在此選用這個例子是為了說明訊息傳遞的原理。之後,我們會挖掘些可以通過web worker獲得益處的實際用例。
終止Workers
worker屬於佔用資源密集型,它們屬於系統層面的執行緒。因此,你應該不希望建立太多的worker執行緒,所以你需要在它完成任務後終止它。Worker可以通過如下方式由自己終止:
self.close();
或者,由父執行緒終止。
primeWorker.terminate();
安全與限制
在worker的程式碼中,不要訪問一些重要的JavaScript物件,如document、window、console、parent,更重要的是不要訪問DOM物件。也許不用DOM元素以至不能更新頁面元素聽上去有點嚴格,但是這是一個重要的安全設計決定。
想象一下,如果眾多執行緒都試著去更新同一個元素那就是個災難。所以,web worker需要處在一個嚴格的併執行緒安全的環境中。
正如之前所說,你可以通過worker處理資料,並將結果返回主執行緒,進而更新DOM元素。儘管它們不能訪問一些重要的JavaScript物件,但是它們可以呼叫一些函式,如setTimeout()/clearTimeout()、setInterval()/clearInterval()、navigator等等,也可以訪問XMLHttpRequest和localStorge物件。
同源限制
為了能和伺服器互動,worker必須遵守同源策略(same-origin policy)(譯註:可參考國人文章同源策略)。比如,位於http://www.example.com/內的指令碼檔案不能訪問https://www.example.com的指令碼。儘管域名相同,但同源策略要求埠也必須一致。通常,這不會成為一個很大的問題。但是你很有可能會同一個域名編寫worker和client,所以知道這點對你總是有所幫助。
Google Chrome與本地訪問
Google Chrome對worker本地訪問做了限制,因此你無法本地執行這些例子。如果你又想用Chrome,那麼你可以將檔案放到伺服器上,或者在通過命令啟動Chrome時加上–allow-file-access-from-files。例如,蘋果系統下:
$ /Applications/Google\ Chrome.app/Contents/MacOS/Google\ Chrome –allow-file-access-from-files
然而,在實際產品生產過程中,此方法並不推薦。最好還是將你的檔案上傳至伺服器中,同時進行跨瀏覽器測試。
Worker除錯和錯誤處理
不能訪問console似乎有點不方便,但幸虧有了Chrome開發者工具,你可以像除錯其他JavaScript程式碼那樣除錯worker。
為處理web worker丟擲的異常,你可以偵聽error事件,它屬於ErrorEvent物件。檢測該物件從中瞭解引起錯誤的詳細資訊。
primeWorker.addEventListener('error', function(error){ console.log(' Error Caused by worker: '+error.filename + ' at line number: '+error.lineno + ' Detailed Message: '+error.message); });
多個Worker執行緒
儘管建立多個worker來協調任務分配也許很常見,但還是要提醒一下各位,官方規範指出worker屬於相對重量級並能長期執行在後臺的指令碼。所以,由於Web worker的高啟動效能成本和高程式記憶體成本,它們的數量不宜過多。
簡單介紹共享workers
官方規範指出有兩種worker:專用執行緒(dedicated worker)和共享執行緒(shared worker)。到目前為止,我們只列舉了專用執行緒的例子。專用執行緒與建立執行緒的指令碼或頁面直接關聯,即有著一對一的聯絡。而共享執行緒允許執行緒在同源中的多個頁面間進行共享,例如:同源中所有頁面或指令碼可以與同一個共享執行緒通訊。
“建立一個共享執行緒,直接將指令碼的URL或worker的名字傳入SharedWorker建構函式”
兩者最主要的區別在於,共享worker與埠相關聯,以保證父指令碼或頁面可以訪問。如下程式碼建立了一個共享worker,並宣告瞭一個回撥函式以偵聽worker發回的訊息 ,同時向共享worker傳輸一條訊息。
var sharedWorker = new SharedWorker('findPrime.js'); sharedWorker.port.onmessage = function(event){ ... } sharedWorker.port.postMessage('data you want to send');
同樣,worker可以偵聽connect事件,當有客戶端想與worker進行連線時會相應地向其傳送訊息。
onconnect = function(event) { // event.source包含對客戶端埠的引用 var clientPort = event.source; // 偵聽該客戶端發來的訊息 clientPort.onmessage = function(event) { // event.data包含客戶端發來的訊息 var data = event.data; .... // 處理完成後發出訊息 clientPort.postMessage('processed data'); } };
由於它們具有共享的屬性,你可以保持一個應用程式在不同視窗內的相同狀態,並且不同視窗的頁面通過同一共享worker指令碼保持和報告狀態。想更多的瞭解共享worker,我建議你閱讀官方文件。
實際應用場景
worker的實際發生場景可能是,你需要處理一個同步的第三方介面,於是主執行緒需要等待結果再進行下一步操作。這種情況下,你可以生成一個worker,由它代理,非同步完成此任務。
Web worker在輪詢情況下也非常適用,你可以在後臺不斷查詢目標,並在有新資料時向主執行緒傳送訊息。
你也許遇到需要向服務端返回大量的資料的情況。通常,處理大量資料會消極影響程式的響應能力,然後導致不良使用者體驗。更優雅的辦法是將處理工作分配給若干worker,由它們處理不重疊的資料。
還有應用場景會出現在通過多個web worker分析音訊或視訊的來源,每個worker針對專項問題。
結論
隨著HTML5的展開,web worker規範也會持續加入。如果你打算使用web worker,看一看它的官方文件不是壞事。
專項執行緒的跨瀏覽器支援目前還不錯,Chrome,Safari和Firefox目前的版本都支援,甚至IE這次都沒有落後太多,IE10還是不錯的。但是共享執行緒只有當前版本的Chrome和Safari支援。另外奇怪的一點是,Android 2.1的瀏覽器支援web worker,反而4.0版本不支援。蘋果也從iOS 5.0開始支援web worker。
想象一下,在原本單執行緒環境下,多執行緒會帶來無限可能哦~
《深入HTML5 Web Worker應用實踐:多執行緒程式設計》
相關文章
- web workers簡介(一)基礎使用Web
- 深入web workers (上)Web
- Cesium之Web WorkersWeb
- 什麼是 Web Workers?Web
- Inline Workers--Web workers without a separate Javascript fileinlineWebJavaScript
- 精讀《談談 Web Workers》Web
- 開始使用 Python 開發 Web 應用PythonWeb
- 前端效能最佳化:使用 Web Workers 實現輪詢前端Web
- web workers簡介(三)建立subworkerWeb
- HTML5 Web Workers簡介HTMLWeb
- 【深入吧,HTML 5】 效能 & 整合 —— Web WorkersHTMLWeb
- web workers簡介(二)動態建立workerWeb
- Webpack 下使用 web workers 及 基本原理 和 應用場景Web
- JavaScript 工作原理之七-Web Workers 分類及 5 個使用場景JavaScriptWeb
- Java Web學習之旅開始JavaWeb
- 純前端生成Excel檔案騷操作——WebAssembly & web workers前端ExcelWeb
- 關於 Web Workers 你需要了解的 7 件事Web
- Python Web開發:從 wsgi 開始PythonWeb
- Web開發從學些JavaScript開始WebJavaScript
- 開始使用MASMASM
- 什麼是Web workers?為什麼我們需要他Web
- 開始研究web,mark一下Web
- 開始使用ASP.NET Core - 建立第一個Web應用ASP.NETWeb
- JavaScript是如何工作的:Web Workers的構建塊 + 5個使用他們的場景JavaScriptWeb
- [譯] Service workers:Progressive Web Apps 背後的小英雄WebAPP
- 3D拓撲自動佈局之Web Workers篇3DWeb
- 開始使用WAMPServerServer
- ImageJ使用教程(一):開始使用
- Mozilla Firefox開始支援Web元件技術FirefoxWeb元件
- NestJS WebSocket 開始使用JSWeb
- 使用Python開始機器學習Python機器學習
- 開始使用C# (轉)C#
- [Flutter翻譯]開始使用Flutter Web之前應該知道的7件事FlutterWeb
- HTML5:用Web Workers API執行大計算量任務HTMLWebAPI
- 從零開始構建Web應用-PART 1Web
- 從零開始寫Java Web框架——maven 外掛JavaWeb框架Maven
- 系列文章——Web API從開始到結束WebAPI
- Go 模組--開始使用 Go ModulesGo