本文首發在alloyteam團隊博客,連接地址http://www.alloyteam.com/2015/11/deep-in-web-worker/javascript
上一篇文章《從setTimeout說事件循環模型》從setTimeout入手,探討了Javascript的事件循環模型。有別於Java/C#等編程語言,Javascript運行在一個單線程環境中,對setTimeout/setInterval、ajax和dom事件的異步處理是依賴事件循環實現的。做爲一個轉向Javascript的開發人員,很天然的產生一個疑問,如何實現Javascript多線程編程呢?隨着學習的深刻,我瞭解到HTML5 Web Worker,本文將分析Web Worker爲Javascript帶來了什麼,同時帶你們看看worker模型在其餘語言的應用。html
1.Web Worker是什麼html5
Web Worker 是HTML5標準的一部分,這一規範定義了一套 API,它容許一段JavaScript程序運行在主線程以外的另一個線程中。Web Worker 規範中定義了兩類工做線程,分別是專用線程Dedicated Worker和共享線程 Shared Worker,其中,Dedicated Worker只能爲一個頁面所使用,而Shared Worker則能夠被多個頁面所共享,本文示例爲專用線程Dedicated Worker。java
1.1 API快速上手web
使用Dedicated Worker的主頁面代碼main.jsajax
var worker = new Worker("task.js"); worker.postMessage( { id:1, msg:'Hello World' } ); worker.onmessage=function(message){ var data = message.data; console.log(JSON.stringify(data)); worker.terminate(); }; worker.onerror=function(error){ console.log(error.filename,error.lineno,error.message); }
Dedicated Worker所執行的代碼task.js編程
onmessage = function(message){ var data=message.data; data.msg = 'Hi from task.js'; postMessage(data); }
在main.js代碼中,首先經過調用構造函數,傳入了worker腳本文件名,新建了一個worker對象,在個人理解中,這一對象是新建立的工做線程在主線程的引用。隨後調用worker.postMessage()方法,與新建立的工做線程通訊,這裏傳入了一個json對象。隨後分別定義了worker對象的onmessage事件和onerror事件的回調處理函數,當woker線程返回數據時,onmessage回調函數執行,數據封裝在message參數的data屬性中,調用 worker 的 terminate()方法能夠終止worker線程的運行;當worker線程執行出錯時,onerror回調函數執行,error參數中封裝了錯誤對象的文件名、出錯行號和具體錯誤信息。json
在task.js代碼中,定義了onmessage事件處理函數,由主線程傳入的數據,封裝在message對象的data屬性中,數據處理完成後,經過postMessage方法完成與主線程通訊。在工做線程代碼中,onmessage事件和postMessage方法在其全局做用域能夠訪問。canvas
1.2 worker線程執行流程瀏覽器
經過查閱資料,webKit加載並執行worker線程的流程以下圖所示
1) worker線程的建立的是異步的
代碼執行到"var 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的消息隊列中;
1.3 worker線程數據通信方式
主線程與子線程數據通訊方式有多種,通訊內容,能夠是文本,也能夠是對象。須要注意的是,這種通訊是拷貝關係,便是傳值而不是地址,子線程對通訊內容的修改,不會影響到主線程。事實上,瀏覽器內部的運行機制是,先將通訊內容串行化,而後把串行化後的字符串發給子線程,後者再將它還原。
主線程與子線程之間也能夠交換二進制數據,好比File、Blob、ArrayBuffer等對象,也能夠在線程之間發送。可是,用拷貝方式發送二進制數據,會形成性能問題。好比,主線程向子線程發送一個50MB文件,默認狀況下瀏覽器會生成一個原文件的拷貝。爲了解決這個問題,JavaScript容許主線程把二進制數據直接轉移給子線程,轉移後主線程沒法再使用這些數據,這是爲了防止出現多個線程同時修改數據的問題,這種轉移數據的方法,叫作Transferable Objects。
// Create a 32MB "file" and fill it. var uInt8Array = new Uint8Array(1024*1024*32); // 32MB for (var i = 0; i < uInt8Array .length; ++i) { uInt8Array[i] = i; } worker.postMessage(uInt8Array.buffer, [uInt8Array.buffer]);
1.4 API進階
在worker線程中,能夠得到下列對象
1) navigator對象
2) location對象,只讀
3) XMLHttpRequest對象
4) setTimeout/setInterval方法
5) Application Cache
6) 經過importScripts()方法加載其餘腳本
7) 建立新的Web Worker
worker線程不能得到下列對象
1) DOM對象
2) window對象
3) document對象
4) parent對象
上述的規範,限制了在worker線程中得到主線程頁面相關對象的能力,因此在worker線程中,不能進行dom元素的更新。
2. 似曾相識worker模型
我在學習Web Worker過程當中,總有一種似曾類似的感受。在以往的學習經驗中,瞭解過Java Swing GUI庫中的Swing Worker,咱們能夠看看worker模型在Swing中的應用。
2.1 Swing事件分發模型
同Winform/WPF等其餘GUI庫同樣,Swing是一個基於事件隊列的單線程編程模型。Swing將GUI請求放入一個事件隊列EventQueue 中等待執行,EventQueue的派發機制由單獨的一個線程管理,這個線程稱爲事件派發線程(EventDispatchThread),負責GUI組件的繪製和更新。這一事件分發模型以下圖所示:
Swing單線程模型的一個問題是,若是在「事件派發線程」上執行的運算太多,那麼GUI界面就會停住,系統響應和運算就會很是緩慢。
既然事件派發線程是爲了處理GUI事件而設的,那麼,咱們只應該把GUI事件處理相關的代碼,放在事件派發線程中執行。其餘與界面無關的代碼,應該放在Java其餘的線程中執行。這樣,咱們在Swing的事件處理中,仍然使用Swing的單線程編程模型,而其餘業務操做均使用多線程編程模型,這就能夠大大提升Swing程序的響應和運行速度,充分運用Java多線程編程的優點。
2.2 Swing Worker
Java SE 6提供了javax.swing.SwingWorker類,Swing Worker 設計用於須要在後臺線程中運行長時間運行任務的狀況,並可在完成後或者在處理過程當中向 UI 提供更新。
假定咱們在UI界面點擊一次下載按鈕,在按鈕的事件處理函數中,須要去加載一張Icon圖片,圖片加載完成後,將icon在UI界面展現出來。
SwingWorker testWorker = new SwingWorker<Icon , Void>(){ @Override protected Icon doInBackground() throws Exception { Icon icon = retrieveImage(strImageUrl); return icon; } protected void done(){ Icon icon= get(); lblImage.setIcon(icon); //lblImage可經過構造函數傳入 } } testWorker.execute();
上述代碼中,咱們在按鈕的事件處理函數中,建立了一個swingworker實例對象。調用構造函數時,指定第一個泛型參數爲Icon,這是一個自定義類型,這裏表明一個Icon圖片對象。指定這一泛型參數,是爲了指定doInBackground()方法的返回值,並在done()方法中獲取。
doInBackground方法做爲工做線程的一部分執行,它負責完成線程的基本任務,並以返回值來做爲線程的執行結果。在doInBackground方法完成以後,SwingWorker調用done方法。若是任務須要在完成後,使用工做線程執行結果來更新GUI組件或者作些清理工做,可覆蓋done方法來完成它們。使用SwingWorker的get方法能夠獲取doInBackground方法的結果,done方法是調用get方法的最好地方,由於此時已知道線程任務完成了,SwingWorker在EDT上激活done方法,所以能夠在此方法內安全地和任何GUI組件交互。execute方法是異步執行,它當即返回到調用者。在execute方法執行後,EDT當即繼續執行。
2.3 WebWorker vs SwingWorker
Swing Worker還有一些其餘的方法,這裏再也不討論。咱們能夠結合Web Worker,對比看看二者異同。
二者編程模型相同,都是在主線程中,將耗時工做交由工做線程去異步的完成,從而避免主線程的阻塞。
二者線程通訊機制不一樣,Web Worker線程通訊限制嚴格,僅能經過postMessage方法通訊,並且參數傳遞均爲值傳遞,沒有引用傳遞;Swing Worker參數傳遞靈活,上述事例中,testWorker的doInBackground方法直接引用了strImageUrl變量,不過這一方式並不推薦,而是應當定義一個新類繼承自SwingWorker,並在構造函數中傳入imgUrl變量,而後在實例化worker線程中傳入變量。
二者對UI界面的更新限制不一樣,Web Worker禁止在worker線程中操做dom元素,因此不能在worker中更新UI;Swing Worker容許在done方法中更新UI,這裏並無違背Swing的事件分發模型,由於最終仍是在EDT上激活的done方法,依然遵循着事件分發模型。
3. Web Worker帶來了什麼
最後來總結Web Worker爲javascript帶來了什麼,學習過程當中,看到一些文章認爲Web Worker爲Javascript帶來了多線程編程能力,我不承認這種觀點。
3.1 Web Worker帶來後臺計算能力
Web Worker自身是由webkit多線程實現,但它並無爲Javasctipt語言帶來多線程編程特性,咱們如今仍然不能在Javascript代碼中建立並管理一個線程,或者主動控制線程間的同步與鎖等特性。
在我看來,Web Worker是worker編程模型在瀏覽器端Javascript語言中的應用。瀏覽器的運行時,同其餘GUI程序相似,核心邏輯像是下面這個無限循環:
while(true){ 1 更新數據和對象狀態 2 渲染可視化UI }
在Web Worker以前,Javascript執行引擎只能在一個單線程環境中完成這兩項任務。而在其餘典型GUI框架,如前文Swing庫中,早已引入了Swing Worker來解決大量計算對UI渲染的阻塞問題。Web Worker的引入,是借鑑了worker編程模型,給單線程的Javascript帶來了後臺計算的能力。
3.2 Web Worker典型應用場景
既然Web Worker爲瀏覽器端Javascript帶來了後臺計算能力,咱們即可利用這一能力,將無限循環中第一項「更新數據和對象狀態」的耗時部分交由Web Worker執行,提高頁面性能。
部分典型的應用場景以下
1) 使用專用線程進行數學運算
Web Worker最簡單的應用就是用來作後臺計算,而這種計算並不會中斷前臺用戶的操做
2) 圖像處理
經過使用從<canvas>或者<video>元素中獲取的數據,能夠把圖像分割成幾個不一樣的區域而且把它們推送給並行的不一樣Workers來作計算
3) 大量數據的檢索
當須要在調用 ajax後處理大量的數據,若是處理這些數據所需的時間長短很是重要,能夠在Web Worker中來作這些,避免凍結UI線程。
4) 背景數據分析
因爲在使用Web Worker的時候,咱們有更多潛在的CPU可用時間,咱們如今能夠考慮一下JavaScript中的新應用場景。例如,咱們能夠想像在不影響UI體驗的狀況下實時處理用戶輸入。利用這樣一種可能,咱們能夠想像一個像Word(Office Web Apps 套裝)同樣的應用:當用戶打字時後臺在詞典中進行查找,幫助用戶自動糾錯等等。
參考文章
1.The Basics of Web Workers
http://www.html5rocks.com/en/tutorials/workers/basics/
2. 深刻 HTML5 Web Worker 應用實踐:多線程編程
http://www.ibm.com/developerworks/cn/web/1112_sunch_webworker/index.html
3. JavaScript 工做線程實現方式
http://www.ibm.com/developerworks/cn/web/1105_chengfu_jsworker/index.html
4.HTML5 與 」性工能「障礙
http://fins.iteye.com/blog/1747321
5.Web Worker在WebKit中的實現機制
http://blog.csdn.net/codigger/article/details/40581343
6. SwingWorker的用法
http://blog.csdn.net/vking_wang/article/details/8994882