本文主要會談到
開發者會利用函式(function)的方式將程式碼切成一個個片段,而這些函式執行的時機是由一種排程機制所控管的-由「事件迴圈」來控制。事件迴圈是一種類似佇列(queue)的機制,用來管理何時執行哪一個函式。當要執行這個函式時,就將它放到事件迴圈中等候執行。例如:發送 ajax 後要執行某個 callback,於是告訴宿主環境在收到回應時將這個 callback 放到 queue 中準備執行。另外,每一次 loop 稱為一個 tick,每個 tick 會取出 queue 中的 job,也就是要執行的函式來執行。
...
...
一種很多人在排隊的 feel ~
...
...
如下所示,這是一個事件迴圈選取工作執行的虛擬碼。eventLoop 是一個佇列,存放準備要執行的工作,每次會從中取出一個工作來執行,而這個迴圈是常駐的,永遠沒有結束的時候。
var eventLoop = [];
var event;
while(true) {
// 開始本次 tick ...
if(eventLoop.length > 0) {
// 取得工作
event = eventLoop.shift();
// 執行工作
try {
event();
} catch (err) {
reportError(err);
}
}
}
圖解。
共時是指兩個以上的行程(process)在同一時間內同時執行。但對於事件迴圈佇列來說,工作是循序執行的,不會同一瞬間執行多個工作,共時的行程可以彼此沒有互動或有互動。
若有兩個行程 P1 與 P2 在一時間區段內交互執行,也就是有互動的狀況,P1 發出請求 Req i 後,P2 會依照回應 Res i 做出相對應的處理。也就是說,當 P1 與 P2 開始執行後,會有以下的請求和回應:
Req 1, Res 1, Req 2, Req 3, Res 3, Res 2
由此可知,請求和回應的順序是無法預期的。
關於共時的資源利用,有以下兩個議題:「值的共用」和「合作式共時」。
使用一個全域變數(global variable)儲存結果,例如:「result」,P1 與 P2 一起存取 result。這裡要用一些小技巧來避免競爭狀態(race condition),例如:使用閘門(gate)或閂鎖(latch)。
...
...
共用吃一顆蘋果?
...
...
var isDone = false; // flag
if(!isDone) {
isDone = true;
// 執行工作
}
目前執行的工作耗費太多時間,形成獨佔資源的狀態,導致其他工作必須長時間等待,解決方法就是將這個工作切割成片段,逐步完成。
將長時間佔用資源的工作切割成多個子工作,逐步完成,好處是讓其他工作能排入事件迴圈佇列。對使用者來說,有種工作很快被完成的錯覺。
圖片來源:Vida Diamante: Como Administrar Bien Tu Tiempo
...
...
在下例中,發送一個 ajax request,再由 response 這個 callback 處理回傳結果。由於回傳後的結果 data 的資料量可能非常大,處理起來需要花費非常久的時間,因此先切割成兩份,前一千份資料放在 chunk 中,其餘資料仍放在 data 中,然後先對 chunck 做處理,待之後再使用 setTimeout(function() { ... }, 0 )
這個 hack 的方式將剩餘的資料和其處理工作加入事件迴圈佇列中。
var res = [];
function response(data) {
var chunk = data.splice(0, 1000);
res = res.concat(chunk.map(function(val) {
return val * 2;
}));
if(data.length > 0) {
setTimeout(function() {
response(data);
}, 0);
}
}
ajax('http://some.url', response);
這邊要特別說明,使用 setTimeout(function() { ... }, 0 )
加入事件迴圈佇列並無法確定執行時間,只是將這個工作放到目前隊伍(工作佇列,job queue)的尾端,待下一個 tick 優先執行,這是一個代辦清單的概念,是一種插隊的行為。
console.log('A');
setTimeout(function() {
console.log('B');
}, 0);
schedule(function() {
console.log('C');
schedule(function() {
console.log('D');
});
});
假設 schedule 是一個假想的事件迴圈佇列排程的 API,使用 setTimeout(function() { ... }, 0 )
加入一個工作 B,B 會放到目前佇列的尾端,等待下一個 tick 優先執行。
得到 A -> C -> D -> B。
看完這篇文章,我們到底有什麼收穫呢?藉由本文可以理解到...
同步發表於部落格。
關於 event loop,強力推薦這個影片:https://youtu.be/8aGhZQkoFbQ
感謝推薦!