看thinkjs源碼的時(shí)候發(fā)現(xiàn)下面這段代碼。
cluster.on('exit', worker => {
think.log(new Error(think.locale('WORKER_DIED', worker.process.pid)), 'THINK');
process.nextTick(() => cluster.fork());
});
這段代碼的意思很簡(jiǎn)單,就是cluster掛了以后重新fork一個(gè)窜护。
??但是注意到其中的process.nextTick(() => cluster.fork());
這行,剛開始想了一下沒有理解為什么不直接fork
,后面仔細(xì)想了一下矗愧,發(fā)現(xiàn)如果直接fork
,在fork
的過程中又出現(xiàn)錯(cuò)誤導(dǎo)致進(jìn)程退出郑原,而cluster
又監(jiān)聽到exit
的事件唉韭,就會(huì)不斷的重復(fù)這個(gè)過程,阻塞Node進(jìn)程犯犁。
??如果使用process.nextTick(() => cluster.fork());
則不會(huì)阻塞Node的事件循環(huán)属愤,只會(huì)在Event Loop
的close callbacks
階段執(zhí)行fork
,即使程序一直fork
失敗也不會(huì)導(dǎo)致程序假死酸役。(如果有疑問可以閱讀文章末的擴(kuò)展閱讀)住诸。
??下面的Demo說明了為什么使用了nextTick
不會(huì)導(dǎo)致程序假死。
var EventEmitter = require('events').EventEmitter;
var event = new EventEmitter();
var count = 0;
var num = 10;
event.on('some_event', function() {
count++;
console.log('some_event 事件觸發(fā)' + count);
if (count < num) {
event.emit('some_event')
}
});
event.emit('some_event');
console.log('what ?')
運(yùn)行這段代碼就會(huì)輸出
some_event 事件觸發(fā)1
some_event 事件觸發(fā)2
some_event 事件觸發(fā)3
some_event 事件觸發(fā)4
some_event 事件觸發(fā)5
some_event 事件觸發(fā)6
some_event 事件觸發(fā)7
some_event 事件觸發(fā)8
some_event 事件觸發(fā)9
some_event 事件觸發(fā)10
what ?
可以發(fā)現(xiàn) what ?
在最后才輸出涣澡。如果把num設(shè)置的非常大就會(huì)報(bào)錯(cuò)
internal/process/next_tick.js:148
nextTickQueue.push({
^
RangeError: Maximum call stack size exceeded
V8不斷的向事件隊(duì)列里添加任務(wù)贱呐,最終導(dǎo)致出現(xiàn)溢出,把event.emit('some_event')
改寫成
process.nextTick(function(){
event.emit('some_event')
});
就會(huì)發(fā)現(xiàn)輸出成了
ome_event 事件觸發(fā)1
what ?
some_event 事件觸發(fā)2
some_event 事件觸發(fā)3
some_event 事件觸發(fā)4
some_event 事件觸發(fā)5
some_event 事件觸發(fā)6
some_event 事件觸發(fā)7
some_event 事件觸發(fā)8
some_event 事件觸發(fā)9
some_event 事件觸發(fā)10
what ?
并不會(huì)被阻塞入桂,而且無論num
改成多少奄薇,都不會(huì)出現(xiàn)棧溢出的錯(cuò)誤。
??Node的Event loop
執(zhí)行流程如圖
┌───────────────────────┐
┌─>│ timers │
│ └──────────┬────────────┘
| nextTick(隊(duì)列執(zhí)行)
│ ┌──────────┴────────────┐
│ │ I/O callbacks │
│ └──────────┬────────────┘
| nextTick(隊(duì)列執(zhí)行)
│ ┌──────────┴────────────┐
│ │ idle, prepare │
│ └──────────┬────────────┘
| nextTick(隊(duì)列執(zhí)行) ┌───────────────┐
│ ┌──────────┴────────────┐ │ incoming: │
│ │ poll │<─────┤ connections, │
│ └──────────┬────────────┘ | |
| nextTick(隊(duì)列執(zhí)行) │ data, etc. │
│ ┌──────────┴────────────┐ └───────────────┘
│ │ check │
│ └──────────┬────────────┘
| nextTick(隊(duì)列執(zhí)行)
│ ┌──────────┴────────────┐
└──┤ close callbacks │
└───────────────────────┘
直接event.emit('some_event')
的時(shí)候抗愁,Node不斷的把收集到的事件塞到I/O callbacks
這個(gè)隊(duì)列馁蒂,如果有大量的事件塞入就會(huì)最終導(dǎo)致溢出,就是上面的Maximum call stack size exceeded
錯(cuò)誤蜘腌。
??如果加了process.nextTick
則會(huì)不斷的把emit
的事件回調(diào)加到nextTickQueue
隊(duì)列沫屡,在各個(gè)主隊(duì)列切換的時(shí)候執(zhí)行,見上圖的 nextTick(隊(duì)列執(zhí)行)
撮珠。上面的那段Demo把event.emit('some_event')
修改后的執(zhí)行順序就是
??1谁鳍、發(fā)送事件
??2、把事件回調(diào)函數(shù)添加到nextTickQueue
(注意,這個(gè)時(shí)候nextTickQueue
隊(duì)列里只有一個(gè)事件回調(diào)函數(shù)倘潜,如果當(dāng)前隊(duì)列尚未執(zhí)行完畢并且沒有發(fā)生切換绷柒,則nextTickQueue
隊(duì)列里的事件永遠(yuǎn)不會(huì)執(zhí)行)
??3、執(zhí)行nextTickQueue
里的第一個(gè)事件回調(diào)(當(dāng)前隊(duì)列執(zhí)行完畢或者執(zhí)行到一定數(shù)量發(fā)生切換時(shí)涮因,事件回調(diào)又會(huì)重新創(chuàng)建一個(gè)新的nextTickQueue
隊(duì)列并添加一個(gè)事件回調(diào))
??4废睦、然后同上
??這樣就沒有阻塞Node的事件循環(huán),無論num多大都不會(huì)撐爆I/O callbacks
隊(duì)列养泡。其實(shí)最核心的思想就是將任務(wù)拆解到若干次事件循環(huán)中嗜湃,逐步執(zhí)行。
擴(kuò)展閱讀
??Node.js的event loop及timer/setImmediate/nextTick
??Node.js 原理簡(jiǎn)介
??深入理解Node.js:核心思想與源碼分析