Effective JavaScript 開發教程之不要阻塞I/O事件隊列

Effective JavaScript 開發教程之不要阻塞I/O事件隊列

js程序是構建在事件之上的。輸入可能來自不同的外部源。在一些語言中,我們習慣地編寫代碼來等待某個特定的輸入。

var text=downloadSync('https://example.com/file.txt');
console.log(text);

像這樣的形式downloadSync稱為同步函數(或阻塞函數)。程序會停止做任何工作,而等待它的輸入。在這個例子中,也就是等待從網絡上下載文件的結果。由於在等待下載完成的期間,計算機可以做其他有用的工作,因此這樣的語言通常為程序員提供一種方法來創建多個線程,即並行執行子計算。它允許程序的一部分停下來等待(阻塞)一個低速的輸入,而程序的另一部分可以繼續進行獨立的工作。
在js中,大多的I/O操作都提供瞭異步的或非阻塞的API。程序員提供一個回調函數,一旦輸入完成就可以被系統調用,而不是程序阻塞在等待結果的線程上。

var text=downloadSync('https://example.com/file.txt',function(text){
  console.log(text);
});

該API初始化下載進行,然後在內部註冊表中存儲瞭回調函數後立即返回,而不是被網絡請求阻塞。當下載完成之後,系統會將下載完的文件的文本作為參數調用該已註冊的回調函數。
隨著事件的發生,事件被添加到應用程序的事件隊列的末尾。js系統使用一個內部循環機制來執行應用程序。該循環機制每次都拉取隊列底部的事件,以接收到這些事件的順序來調用這些已經註冊的js事件處理程序,並將事件的數據作為該事件處理程序的參數。
運行到完成機制擔保的好處是當代碼運行時,你完全掌握應用程序的狀態。根本不必擔心一些變量和對象屬性的改變由於並發執行代碼而超出你的控制。並發編程在js中往往比使用線程和鎖的c++,java或c#更容易。
然而,運行到完成機制的不足是,實際上所有你編寫的代碼支撐著餘下應用程序的繼續執行。像瀏覽器這樣的交互式應用程序中,一個阻塞的事件處理程序會阻塞任何將被處理的其他用戶輸入,甚至可能阻塞一個頁面的渲染,從而導致頁面失去響應的用戶體驗。在服務器環境中,一個阻塞的事件處理程序可能會阻塞將被處理的其他網絡請求,從而導致服務器失去響應。
js並發的一個最重要的規則是絕不要在應用程序事件隊列中使用阻塞I/O的API。在瀏覽器中,甚至幾乎沒有任何阻塞API是可用的,盡管有一些平臺實現瞭。提供類似於downloadAsync功能的網絡I/O的XMLHttpRequest庫有一個同步的版本實現,被認為是不好的。對於web應用程序的交互性,同步的I/O會導致災難性的後果,它在I/O操作完成之前一直會阻塞用戶與頁面的交互。
相比之下,異步的API用在基於事件的環境中是安全的,它們迫使應用程序邏輯在一個獨立的事件循環“輪詢”中繼續處理。如上面的例子,假設需要幾秒鐘來下載網絡資源,在這段時間裡,數量龐大的其他事件很可能發生。在同步的實現中,這些事件就會堆積在事件隊列中,而事件循環將停留等待該JS代碼執行完成,這將阻塞任何其他事件的處理。在異步的版本中,JS代碼註冊一個事件處理程序並立即返回,這將在下載完成之前,允許其他處理程序處理這期間的事件。
在主應用程序事件隊列不受影響的環境中,阻塞操作很少出問題。例如,web平臺提供瞭Worker的API,該API使得產生大量的並行計算成為可能。不同於傳統的線程執行,Workers在一個完全隔離的狀態下執行,沒有獲取全局作用域或應用程序主線程web頁面內容的能力。因此,它們不會妨礙主事件隊列中運行的代碼的執行。在一個Worker中,使用XMLHttpRequest同步的變種很少出問題。下載操作的確會阻塞Worker繼續執行,但這並不會阻止頁面的渲染或事件隊列中的事件響應。在服務器端環境中,阻塞的API在啟動一開始是沒有問題的,也就是在服務器開始響應輸入的請求之前。然後在處理請求期間,瀏覽器事件隊列中存在阻塞的API就是有問題的啦。 提示

異步API使用回調函數來延緩處理代價高昂的操作以避免阻塞主應用程序

js並發地接收事件,但會使用一個事件隊列按序地處理事件處理程序

在應用程序事件隊列中絕不要使用阻塞的I/O

發佈留言

發佈留言必須填寫的電子郵件地址不會公開。 必填欄位標示為 *