1. 程式人生 > 其它 >課外加餐 2 | 任務排程:有了 setTimeOut,為什麼還要使用 rAF?

課外加餐 2 | 任務排程:有了 setTimeOut,為什麼還要使用 rAF?

前言:該篇說明:請見 說明 —— 瀏覽器工作原理與實踐 目錄

 

  都知道,要想利用 JS 實現高效能的動畫,那就得使用 requestAnimationFrame 這個 API,簡稱 rAF,那為什麼都推薦使用 rAF 而不是 setTimeout 呢?

  要解釋清楚這個問題,就要從渲染程序的任務排程系統講起,理解了渲染程序任務排程系統,自然就明白了 rAF 和 setTimeout 的區別。其次,如果理解任務排程系統,就能將渲染流水線和瀏覽器架構等知識串起來,理解了這些概念也有助於理解 Performance 標籤是如何工作的。

  要想了解最新 Chrome 的任務排程系統是怎麼工作的,就得先來回顧下之前介紹的訊息迴圈系統。渲染程序內部的大多數任務都是在主執行緒上的,諸如 JS 執行、DOM、CSS、計算佈局、V8 的垃圾回收等任務。要讓這些任務能夠在主執行緒上有條不紊的執行,就需要引入訊息佇列。

  在《16 | WebAPI:setTimeout 是如何實現的?》一文中還介紹了,主執行緒維護了一個普通的訊息佇列和一個延遲訊息佇列,排程模組會按照規則依次取出這兩個訊息佇列中的任務,並在主執行緒上執行。為了下文講訴方便,在這裡把普通的訊息佇列和延遲佇列都當成一個訊息佇列。

  新的任務都是被放進訊息佇列中去的,然後主執行緒再依次從訊息佇列中取出這些任務來順序執行。這就是之前介紹的訊息佇列和事件迴圈系統。

 

單訊息佇列的隊頭阻塞問題

  渲染主執行緒會按照先進先出的順序執行訊息佇列中的任務,具體地講,當產生了新的任務,渲染程序會將其新增到訊息佇列尾部,在執行任務過程中,渲染程序會順序地從訊息佇列頭部取出任務並依次執行。

  在最初,採用這種方式並沒有太大的問題,因為頁面中的任務還不算太多,渲染主執行緒也不是太繁忙。不過瀏覽器是向前不斷進化的,其進化路線體現在架構的調整、功能的增加以及更加精細的優化策略等方面,這些變化讓渲染程序所需要處理的任務變多了,對應的渲染程序的主執行緒頁變得越擁擠。下圖展示的僅僅是部分執行在主執行緒上的任務:

 

任務和訊息佇列

  你可以試想下,在基於這種單訊息佇列的架構下,如果使用者發出一個點選事件或縮放頁面的事件,而在此時,該任務前面可能還有很多不太重要的任務在排隊等待著被執行,諸如V8 的垃圾回收、DOM 定時器等任務,如果執行這些任務需要花費的時間過久的話,那就會讓使用者產生卡頓的感覺。參考下圖:

 

隊頭阻塞問題

  因此,在單訊息佇列架構下,存在著低優先順序任務會阻塞高優先順序任務的情況,比如在一些效能不高的手機上,有時候滾動頁面需要等待一秒以上。這像極了在介紹 HTTP 協議時所談論的隊頭阻塞問題,那我們也把這個問題稱為訊息佇列的隊頭阻塞問題吧。

 

Chromium 是如何解決隊頭阻塞問題的?

  為了解決由於單訊息佇列而造成的隊頭阻塞問題,Chromium  團隊從 2013 年到現在,花了大量的精力在持續重構底層訊息機制。在接下來的篇幅裡,我會按照 Chromium 團隊的重構訊息系統的思路,來分析下他們是如何解決掉隊頭阻塞問題的。