瀏覽器上的threading - web workers

2020-05-10

集合吧!threading 厨們!!

  • threading 與我: 在一開始學應用程式開發的時候接觸 Android programing, 後來也自己摸過一點點 swift,也寫過 JAVA swing(年代久遠的一個東西...用 JAVA 做 desktop 的應用程式),在寫 JAVA server 的時期也會用各種多執行緒的 API(如ExecutorService,跟執行環境要一個 thread-pool)同時用多個執行緒來加速流程的進行(同時聯絡其他多台 server, 同時寄信...etc)
  • 那在 web-UI 上的開發呢? web 應用程式開發上以前一直有一個痛點,就是沒辦法自己開執行緒,很多時候開發者們也覺得好像沒那個必要,因為 js 在做各種 network-io(就是指打 rest API)的時候,執行環境所提供的 API(XMLHttpRequest, fetch)天生就是非堵塞的(non-blocking),在做 network-io 的時候背後是瀏覽器在進行,JS 的 mainThread 並不會被堵塞住,這些工作都是io-bound的,那如果今天要做的事情是cpu-bound的話呢?

cpu-bound task

對資料跑迴圈做某種處理,access 某個變數再取底下的某個屬性的值......這樣子的事情其實都是 cpu 在做事,但是因為今天的裝置的 cpu 運算速度都非常快,在一般處理生活大小事的應用程式上(所以我指的不是畫圖畫很大的那種電子遊戲),只要不是變成無限迴圈的狀況的話,通常 UI 也不會卡住停下來的感覺,但是要是我們今天要做某些非常激烈的數學運算,像是從100萬筆資料中找到最小的 100 個數,然後再把它們相加....之類的事,要是直接在 JS 上的 mainThread 上做,就會造成程式執行到那個地方的時候卡在那邊做那個運算(其實是因為遍歷那 100 萬筆資料太花時間),就沒辦法回應 UI 上的反應(如文字輸入,輸入游標甚至不會閃爍,會整個停止), 這就是一個在極端狀況下可能會遇到的問題。在 HTML5 的標準提出的時候,就已經有了 Web Workers 這樣的提案,並且後來陸續被瀏覽器們實作了。

Web Wrokers api

  • mdn - web workers
  • 它是HTML5裡的標準之一,所以其實是已經被實作好一段時間的 API 了。
  • 從上面的文件中可以看到,這是個可以讓開發者自己決定什麼時候要開啟一個新的執行緒來做事的 API,當然它是不能夠直接去操作畫面的,這在各種 UI-programing 上都是這樣,能夠直接操作畫面的只有 mainThread。
  • 要在 workerThread 上做的事情要寫在一個獨立的 script 裡,然後在創出 workerThread 時去載入那個 script。
  • 它必須要使用一種叫postMessage的 API 來做 mainThread 與 workerThread 之間的聯絡。這也造成它使用上非常地繁瑣。
  • 作者: Surma
  • Surma 是Google developer Advocate - web team 裡的其中一人,Jake Archibald的同事。兩人常常一起在Google Chrome Developers這個 youtube 頻道上的 http 203單元上出現
  • 在 2019/05 的 Google I/O 上, Google developer Advocator web team 就一起發表了一個 project - PROXX,它是一個在瀏覽器上玩的踩地雷遊戲,為了讓遊戲體驗更順暢,它們選擇讓各種計算都在 worker threads 上去做,以讓 main thread 能夠完全專心在更新畫面與跟 user 互動
  • 簡化流程,可以不用使用不是很好用的 postMessage API 來做執行緒之間的交互。
  • 可以把 讓某件事在web-worker上做這樣的事情直接模組化(想到就讓人衝動)。
  • 讓載入某件事的 code 這樣的事可以用 import 的(在用 webpack 打包的情況的話,須搭配 worker-plugin這個 plugin)

使用感想

  • 我在工作上的某次需要做到有點份量的數學運算的程式裡測試用來玩玩看,該專案 react based。
  • 用了之後再用 lighthouse 去測 performance,其實first cpu idle time並沒有比較提昇,因為其實我的程式裡的運算量還不算大,就算不使用 web workers,只要計算過程的 code 不要寫得太差的話,在 mainThread 上其實算不到 0.1 秒
  • 但是我有測試要是我故意讓計算過程會算很久,好比 20 秒以上(例如跑一個 while 迴圈裡面不斷 +=1,直到超過 60000000...才跳出迴圈),效果就真的是非常驚人,就算故意做了這樣的事,UI 也不會有任何的 lag 感(照樣可以很順地捲動頁面,如果不使用 web workers 的話,會讓 chrome 跳出要不要終止網頁的提示視窗....)。

注意事項-caveat

  • web-worker 在做的事是寫在一個獨立的 script 中,要 new Worker(script path) 時瀏覽器材會下載那個 script,要是 script 裡 import 很多 library 的話,code 是會被一起整個被打包進單獨的 script 裡,import 越多 library,要下載的 script 就越肥大(而且是無法 code-split 的),改善方式就是用到同樣的 library 時就寫在一起,讓它盡量下一次就好。但也必須注意重新 init 的問題,重新叫 worker 出來時,那獨立的 script 要再重新下載一次(如果之前的 worker 已經 clear 了,必須重新 new 出來,就是要重新下載),但如果瀏覽器會 cache 那個 script,這就不是什麼問題。
  • 另外要注意,在 web worker 的環境底下沒有 window, 有 self,有些 Web API 是無法取用的,如 localstorage。ref

這邊有一篇實戰超級好文,可以直接看這篇來 try

參考

response