Cesium原理篇:4Web Workers剖析

       JavaScript是單線程的,又是異步的,而最新的HTML5中,經過Web Workers能夠在JS中支持多線程開發。這是幾個意思?異步仍是單線程,這怎麼理解?Web Workers又是什麼原理?實際開發中,異步和多線程之間如何交互?答案就在下面。主要涉及的內容有: 算法

  • 爲何異步解決不了問題瀏覽器

  • Worker又是什麼玩法多線程

  • Cesium中的異步+多線程框架併發

爲何異步解決不了問題

       簡單說,JavaScript是單線程的,簡單易用,但若是遇到時間較長的任務時,則容易出現卡死的現象,爲了不這種問題,咱們對時間久的任務採用異步的方式,保證頁面的快速響應。 app

       好比咱們常見的setTimeout,指定某個時間運行,而後在指定時間運行該函數。然而「JS運行在單線程環境中,定時器僅僅是計劃代碼在將來某個時間執行,並不做爲保證執行時間,由於不一樣時間可能有其餘代碼在控制JS進程,而全部函數必須使用相同的線程執行。實際上,由瀏覽器負責排序,指派某段代碼在某個時間點運行的優先級」。在這裏,單線程,異步又該如何理解?這就須要咱們瞭解一下異步的原理。 框架

 

image

摘自《Secrets of theJavaScript Ninja》 異步

       這個圖初看有點晦澀,沉下心來好好看一遍,而後在看看這段文字解釋,相信你會大有收穫。首先,右側是JS引擎所觸發的代碼,左側是事件隊列,0,10,20則是自上而下的時間軸,咱們就以毫秒爲單位吧。 函數

       首先,在2ms處,執行了setTimeout語句,設定10ms後執行fun1函數;在5ms處出現了鼠標點擊事件,執行fun2函數;接着在10ms處出執行了setInterval,設定10ms後執行fun3函數。而整個JS代碼塊執行大約用了18ms。所以,首先當鼠標點擊後的回調時間fun2以及setTimeout所觸發的fun1函數發現,此時JS代碼塊還控制着執行進行,則二者都進入隊列,等待一個合適的時機在運行 oop

       這時,在18ms處,JS代碼塊終於運行完了,機會來了,這時鼠標的callback回調關聯着一個異步事件(由於咱們沒法知道用戶想要什麼時候點擊鼠標,因此咱們認爲回調事件是異步的),因此很不幸,fun1事件仍是要繼續呆在隊列中。同時,在20ms出,觸發了第一次setInterval,固然一視同仁,因此fun3也進入隊列。 requirejs

       28ms處,終於鼠標回調事件結束了,看看隊列裏面,setTimeout的fun1函數終於有了出頭日,開始執行fun1函數,隊列中僅剩下setInterval的fun3函數。在30ms時,setInterval又調用了一次,但發現隊列中上一次的函數還未運行,因此這一次的觸發沒有任何效果,丟棄掉。

       終於36ms後,Time觸發的fun1運行完畢,隊列中僅剩的fun3函數開始運行,在40ms時,setInterval再次週期觸發,但此時js進程仍是由fun3函數控制,因此觸發事件進入隊列。

       以此類推,一直運行到隊列爲空時,這樣一旦有事件觸發,則會直接運行。 但願全部人能認真理解這個過程,並發現setTimeout和setInterval在處理上的相同和不一樣處,這塊不是本文重點,因此很少討論。

       經過這樣一個過程,相信你們理解了異步和單線程之間的關係:JS在一個線程中運行,但經過消息隊列來實現異步調用,但調用自己也是在同一個線程中運行,只是能夠延後或分解任務。舉個不太穩當的例子:假如只有一個出租車司機,至關於JS的進程,模擬一個線程的狀況,而乘客至關於異步請求,經過滴滴打車,能夠約定某個時間來接你,而後到達目的地(函數實現)。但觸發並不等同於運行,乘客下單時,司機還在載其餘客人,但答應在約定時間接你。這時他載完該乘客後立馬去接你,知足你的請求。而在此以前,各自忙各自的,他在執行他的任務,你有可能在等,或者在刷手機(服務端接收請求,並返回結果)。

       異步確實能儘量的優化,好比Ajax等異步請求。但這要求把任務分解的比較簡單,在時間比較久的任務下仍是會出現無響應的問題,無論你的進度條作的有多好看。

Worker又能幹什麼事情

       異步只是看上去更及時而已,但該花的時間一點也不會少,並且由於調度自己的成本,時間還會多花一點。並且,隨着Web應用的不斷髮展 ,在JS端要求的計算量也愈來愈大,這種時候,Web Worker可讓JS在後臺解決這些問題,而沒必要擔憂影響用戶體驗。

       須要注意的是,Worker線程徹底在另外一個做用域中,並且沒法操做DOM元素,不能與網頁代碼共享做用域。但這已經足夠了,好比排序,或者zip壓縮等操做,均可以放到Worker線程來運行,從而可以在Web端進行相似CS的不少應用。

       Worker的具體使用這裏也不介紹,主要解釋一下下面這張圖:

 

image

摘自AlloyTeam團隊《深刻理解Web Worker》

main.js中,在建立woker線程後,當即調用了postMessage方法傳遞了數據,在worker線程還沒建立完成時,main.js中發出的消息,會先存儲在一個臨時消息隊列中,當異步建立worker線程完成,臨時消息隊列中的消息數據複製到woker對應的WorkerRunLoop的消息隊列中,worker線程開始處理消息。在通過一輪消息來回後,繼續通訊時, 這個時候由於worker線程已經建立,因此消息會直接添加到WorkerRunLoop的消息隊列中 ---摘自AlloyTeam團隊《深刻理解Web Worker》

       這是Worker線程和主線程的一個交互方式,首先可見消息的發送和接收採用的是postmessage和onmessage,相信作過MFC開發的一看也能發現,這也是一個異步消息隊列的傳輸方式。

       在數據傳輸中,或許在Worker線程中採用同步,效果會更好。另外,在參數的傳遞是拷貝方式,但同時提供Transferable Objects方式,能夠傳地址(不是拷貝)並加鎖,這是一個很是實用的參數,特別是在比較大的二進制數值運算中。

 

image

 

       若是須要在worker腳本中加載其餘js文件,則使用importScripts函數,這是一個同步過程,因此性能會有影響,不過既然是在工做者線程中,因此也不太嚴重。

       還有一個問題,在產品化的時候如何混淆壓縮這些worker.js腳本,由於咱們須要引入它們,因此形成了這部分代碼很容易format,讓別人下載分析。雖然技術在於分享,畢竟做爲產品,這也是須要考慮的部分,總不能直接源碼提供吧。我看到Google WebGL Earth上有一個方式,採用Blob的思路內嵌Worker。由於我還沒用過,這裏也很少說了,只提供這樣一個思路。

Cesium中的異步+多線程框架

       說了這麼多,下面和你們分享一下Cesium中多線程設計的框架吧,我以爲很專業,但也有些複雜,但複雜的同時帶來了很好的擴展性。簡單來講就是一個插件的思路。

       Cesium中設計到三維球的不少計算,數據量很大,好比地形的三角網,以及參數化的Geometry中vbo的計算,而這些都是在Worker中實現的,參數的傳遞,不一樣類型之間的算法也不一樣,因此設計一個易用且易擴展的Worker框架則顯得很是有必要。

 

image

 

       如上圖,用戶只須要建立一個TaskProcessor,指定具體須要建立線程的類型,好比(圓,面,仍是線),而後調用scheduleTask,裏面是該對象的具體參數,好比圓就是圓心+半徑,這樣便完成了調用過程。那返回結果怎麼接受呢?你們注意最後一行返回的參數Promise,這也是一個Promise的異步方式,用戶天然可以方便的獲取到結果。下面是返回結果的實現。

 

image

 

       固然使用的簡單,多數意味着實現的複雜。這裏主要和你們說一下用戶指定Worker的名字,若是根據名字建立該Worker線程,而且易於擴展,也就是插件的實現思路。

       首先,有一個cesiumWorkerBootstrapper的Worker,全部createWorker都會創建一個cesiumWorkerBootstrapper線程,只是賦予不一樣的參數(name不一樣)。

 

image

 

       而在cesiumWorkerBootstrapper線程中,使用了requirejs,根據指定的路徑和文件名,獲取對應的函數,同時替換的onmessage函數。

       此時,主線程在調用scheduleTask時,會再次發送postmessage,並傳入參數,而此時requirejs已經找到了對應的功能函數。,即替換onmessage的函數。

 

image

 

       而這些函數都是由createTaskProcessorWorker封裝的匿名函數,相似於回調函數,進而實現對應的功能。而且返回指定結果。

       這樣,一個多線程設計框架就完成了,而且經過Promise機制,方便用戶的使用,而內部使用require.js,實現了插件的這樣一個方式。這塊代碼涉及的內容比較多,這裏也是理解思路,具體的細節仍是須要代碼的調試才能更好的理解,這裏也僅僅提供參考。

 

image

相關文章
相關標籤/搜索