【雜談】JS相關的執行緒模型整理

貓毛·波拿巴發表於2020-11-08

1.JS是單執行緒嗎?

是的,到目前為止,JS語言沒有多執行緒的語法,它的執行引擎只支援單執行緒,也就是一個JavaScript程式內只有一個執行緒。

2.事件迴圈什麼?

 事件迴圈就是執行執行緒不斷的從佇列中取任務-處理任務-取任務的過程。事件迴圈運用在很多場景下,例如NIO模型,由一個執行緒負責多個socket的通訊,節省執行緒資源。

3.巨集任務,微任務?

JS執行模型有兩種任務型別:

巨集任務(macrotask)=> 點選事件,定時器(setTimeout, setInterval),IO事件

微任務(microtask)=> 常見的Promise

相應的也有兩個任務佇列:巨集任務佇列和微任務佇列

4.執行執行緒只有一個,兩個任務佇列的任務執行順序如何選擇?

每處理完一個巨集任務,就會處理完微任務佇列中的所有任務

5.任務是誰生成的,誰負責插入佇列?

巨集任務:

  • 頁面按鈕被點選時,瀏覽器執行緒(非JS主執行緒)生成一個onclick的回撥任務插佇列
  • 程式碼執行到setTimeout的時候,瀏覽器定時任務開始計時,等時間到的時候將任務插入佇列
  • 瀏覽器執行緒收到http響應時,根據ajax提供的回撥函式生成任務插入佇列。

備註:setTimeout, ajax等是瀏覽器提供的Web API,JS引擎並不包含這些內容,呼叫這些API會將任務資訊告知瀏覽器,瀏覽器執行緒負責任務執行,執行完成時,根據提供的回撥函式生成任務插入佇列等待處理。

微任務:

  • new 一個Promise物件時,會將Promise物件內部的操作包裝成一個任務插入佇列,然後立即返回(因為插入佇列就完事了)。

6.生成器(Generator)和執行器是什麼東西,幹啥用的?

一個函式X內可能有多個操作,這幾個操作要求有序,A->B->C,但又不希望主執行緒一直卡在X函式上(一呼叫X函式就一直等到他結束)。所以A,B,C可能通過巢狀Promise來實現(A的then函式執行B,B的then函式執行C),這樣問題就解決了,即保證了非同步(主執行緒不會卡在函式X上),又保證了有序(函式X內部操作有序)。但是如果函式X內有多個這樣的操作,就會巢狀得很深,程式碼就會很冗長,看起來不舒服。所以就有了生成器和執行器,可以讓函式X的結構更清晰一點。所以它的出現只是為了優化結構。

7.async/await關鍵字是幹啥用的?

簡單的說,這兩個是上面生成器和執行器的語法糖。有了這個,我們就不用再去把一個函式寫成生成器的樣子,也不用自己實現執行器。

8.await關鍵字會阻塞執行緒嗎?

不會,前面說了這個是生成器和執行器的語法糖。執行到await A()那一行程式碼的時候,只是將A函式加入任務佇列,儲存當前上下文,主執行緒跳出當前函式X繼續執行,等到某個時候A函式被執行完成了,就會從該點恢復繼續向下執行。所以await只是語義上的阻塞,並不會實際阻塞執行緒,它只是保證有序。

9.Web Worker是什麼?

前面說了JS是單執行緒的,一個程式內只有一個執行緒。但是可以再開一個程式啊,這樣多執行緒不就有了嘛。瀏覽器就提供這樣的API,能夠讓使用者程式碼通過new Worker的方式再開一個執行緒為使用者服務。Worker與主執行緒通過事件進行互動。Worker執行緒無法訪問DOM。

10.Web Worker的應用場景

  • 用一個Worker來輪詢後端介面查詢有無告警資訊,有變更再通知主執行緒重新整理UI(比較理想的是使用websocket,但是有些場景專門為了一個小功能引入websocket不合適,也有可能後端沒空做)
  • 頁面上某種型別的計算任務較多,主執行緒忙不過來,就會造成任務響應慢,這時候可以將此種型別任務抽取到Worker中進行處理

11.什麼時候會造成頁面“很卡”?

因為瀏覽器的執行模型,就是每隔一段時間重新整理頁面(每個固定時間將重新整理任務插入佇列),而這個重新整理任務也是由主執行緒處理的。而渲染任務優先順序較低,它在每個巨集任務之前執行。也就是前面的巨集任務或微任務佔用了太多時間,頁面就遲遲沒有重新渲染,頁面的重新整理頻率達不到60次/秒,肉眼感覺就會很卡,比如說,你在瀏覽頁面,拉滾動條,發現頁面不會滾或滾得很慢。

12."響應慢"和"卡頓"不是一回事

響應慢是指做了一個操作過了很久才成功,但是頁面還能正常瀏覽。卡頓則是頁面怎麼操作都沒有任何響應。比如同樣重新整理頁面,前者頁面顯示轉圈,轉了很久才載入成功。後者是轉圈直接轉不動了。

13."響應慢"和"卡頓"的優化方式
使用者體驗到的響應慢可能有很多原因,也有相對應的應對方式

  • 後端處理慢 => 後端負責優化(快取,任務拆分並行處理)
  • 資源載入慢 => 考慮客戶端資源快取,CDN
  • 客戶端渲染慢,例如一個幾萬人的組織架構樹  => 考慮增量重新整理

而卡頓的主要原因就是單個任務太耗時了,導致渲染任務的執行頻率達不到。這時候就要對大任務進行拆分,非同步化。例如一個函式包含A和B兩個操作,可使用setTimeout,將一個巨集任務拆成兩個。這樣執行順序就可以變成 A->渲染->B。

//todo 增加一些配圖,更直觀一點

相關文章