並發模型和事件循環

JavaScript 的並發模型是基於 event loop,其在運作上跟 C 或是 Java 有很大的不同。

執行環境概念(Runtime concepts)

下面的內容解釋了一個理論模型,現代 JavaScript 引擎著重實作及優化了描述過後的語意。

視覺化呈現(Visual representation)

Stack, heap, queue

堆疊(Stack)

函式(function)會形成一個 frames 的堆疊。

function f(b){
  var a = 12;
  return a+b+35;
}

function g(x){
  var m = 4;
  return f(m*x);
}

g(21);

當呼叫 g 時,會產生一個含有 g 的參數及區域變數的 frame,而在 g 呼叫了 f 時,含有 f 參數及變數的第二個 frame 就會被置於堆疊的最上面。當 f 返回後,最上面的 frame 會被抽離堆疊,留下 g 的呼叫,然後當 g 返回之後,堆疊就會清空。

堆積(Heap)

物件被分配在一個堆積中,一個只表示記憶體中的一個未架構的大區域。

隊列(Queue)

JavaScript 執行環境包含一個訊息對列,裡面是待處理的訊息,其中每個訊息都與一個 function 相關聯。當堆疊中尚未清空時,會從訊息隊列取一個訊息進行處理,處理過程包含了相關聯的 function。只有當堆疊清空時,該訊息才算是完成處理。 

事件循環(Event loop)

之所以被稱為事件循環,是因為經常被以如下的方式實作:

while(queue.waitForMessage()){
  queue.processNextMessage();
}

當沒有任何訊息時,queue.waitForMessage 會同步地等待新訊息到來。

"執行到完成" ("Run-to-completion")

每一個訊息處理完成之後才會執行下一個。當分析你的程式的時候,上述提供了優秀的特性,像是當一個 Function 開始執行時,他不會被取代且其他程式碼執行前先完成(而且可以修改這個 function 操作的資料)。這特性與 C 不同,在 C 當中,當一個 function 跑在一個線程中,隨時可以被其他線程中的程式碼中止。

這模型的缺點是 - 若是一個訊息要執行很久才完成,網路系統會無法執行一些使用者的基本操作,如點擊按鈕或是捲動頁面。瀏覽器為了要緩解這問題,會跳出視窗"該動作回應時間過久(a script taking too long to run)"。良好的實作方式是縮短執行訊息,若可能的話,將一個訊息切成數個訊息執行。

添加訊息(Adding messages)

瀏覽器中,會添加訊息是由於事件的觸動,以及伴隨著事件的監聽者。若是沒有事件監聽者,則該事件的觸動就不會形成訊息,例如說一個點擊的動作伴隨著點擊事件監聽者就會形成一個新的訊息,其他類事件亦然。

呼叫 setTimeout 時,會在一段時間後添加一個新的訊息到對列,該時間段是以 function 的第二個參數。若對列是清空的,這個訊息會被立刻處理,但若是對列內有其他訊息,setTimeout 訊息必須等到其他處理完。因此該時間段參數只能表示為是最少時間,而不是一個精準的時間。

零延遲(Zero delays)

「零延遲」並非意味著回呼函式會在 0毫秒之後立刻執行。當呼叫 setTimeout 且在時間段參數上用使用 0毫秒並非示程式會過了該段時間就會執行,而是會參考對列中等待的訊息數量。
在下面範例中,"this is just a message"會寫在 setTimeout 的回呼訊息被執行之前,因為該時間段參數是要求執行環境處理所需的最少等待時間,而非一個保證時間。

(function () {

  console.log('this is the start');

  setTimeout(function cb() {
    console.log('this is a msg from call back');
  });

  console.log('this is just a message');

  setTimeout(function cb1() {
    console.log('this is a msg from call back1');
  }, 0);

  console.log('this is the end');

})();

// "this is the start"
// "this is just a message"
// "this is the end"
// "this is a msg from call back"
// "this is a msg from call back1"

多個執行環境的互相溝通
(Several Runtime communicating together)

網路工作者或是跨來源的 iframe都會有各自的堆疊、堆積及訊息對列。兩個特定的執行環境只會通過 postMessage 這個方法來溝通,這個方法,  在其他執行環境有監聽這個 message 時,會產生一個新的訊息到該執行環境的訊息對列中。

絕不阻塞(Never blocking)

事件循環這個模型有一個非常有趣的特色就是永不阻塞,這與其他語言不一樣。I/O的處理通常會經由事件或是回呼函式實作,因此當一個程式正在等待 IndexedDB 的查詢或是回傳 XHR 請求時,依舊可以執行其他動作,像是使用者的輸入。

例外永遠存在,像是 alert 或是同步的 XHR,但好的實作方式就是避開他們。另外要注意個是,例外的例外一直是存在的(exceptions to the exception do exist ) (但通常實作時的錯誤而非其他情況)。

文件標籤與貢獻者

標籤: 
 此頁面的貢獻者: pa-da, enzoyaaaaa
 最近更新: pa-da,