In diesem Artikel werden die Ereignisse in nodejs erläutert und die Unterschiede zwischen setTimeout, setImmediate und process.nextTick erläutert. Es hat einen gewissen Referenzwert. Freunde in Not können sich darauf beziehen. Ich hoffe, es wird für alle hilfreich sein.
Obwohl NodeJS Single-Threaded ist, kann NodeJS Vorgänge an den Systemkernel delegieren. Wenn die Aufgabe abgeschlossen ist, benachrichtigt er NodeJS , löst also die Rückrufmethode in nodejs aus.
Diese Rückrufe werden zur Round-Robin-Warteschlange hinzugefügt und schließlich ausgeführt.
Mit diesem Ereignisschleifendesign können NodeJS endlich nicht blockierende E/A erreichen. [Verwandte Empfehlung: „nodejs Tutorial“]
Die Ereignisschleife in nodejs ist in Phasen unterteilt. Die folgende Abbildung listet die Ausführungsreihenfolge jeder Phase auf:
Jede Phase verwaltet eine Rückrufwarteschlange. Dies ist ein FIFO Warteschlange.
Beim Eintritt in eine Phase werden zunächst die Aufgaben der Phase und dann die zur Phase gehörenden Rückrufaufgaben ausgeführt.
Wenn alle Aufgaben in dieser Rückrufwarteschlange ausgeführt wurden oder die maximale Anzahl von Rückrufausführungen erreicht wurde, geht es in die nächste Phase.
Beachten Sie, dass sich die spezifischen Implementierungen von Windows und Linux geringfügig unterscheiden. Hier konzentrieren wir uns nur auf die wichtigsten Phasen.
Frage: Warum ist es während der Ausführung der Phase notwendig, die maximale Anzahl von Callback-Ausführungen zu begrenzen?
Antwort: In extremen Fällen muss in einer bestimmten Phase möglicherweise eine große Anzahl von Rückrufen ausgeführt werden. Wenn die Ausführung dieser Rückrufe zu lange dauert, wird der Betrieb von NodeJS blockiert. Daher legen wir ein Limit für die Anzahl der Rückrufe fest Ausführungen, um den langen Block von Nodejs zu vermeiden.
Im obigen Bild haben wir 6 Phasen aufgelistet und werden sie im Folgenden einzeln erklären.
Timer
Timer bedeutet auf Chinesisch Timer, was bedeutet, dass eine bestimmte Rückruffunktion zu einem bestimmten Zeitpunkt oder in einem bestimmten Intervall ausgeführt wird.
Es gibt zwei gängige Timer-Funktionen: setTimeout und setInterval.
Im Allgemeinen werden diese Rückruffunktionen nach Ablauf so oft wie möglich ausgeführt, werden jedoch von der Ausführung anderer Rückrufe beeinflusst. Schauen wir uns ein Beispiel an:
rrreeIm obigen Beispiel haben wir someAsyncOperation aufgerufen. Diese Funktion führt zunächst die readFile-Methode aus. Führen Sie dann die Rückruffunktion von readFile aus. Dieser Rückruf wird 10 ms lang ausgeführt. Gehen Sie abschließend zurück und führen Sie den Rückruf in setTimeout aus.
Obwohl im obigen Beispiel festgelegt ist, dass setTimeout nach 100 ms ausgeführt wird, muss es tatsächlich 95 + 10 = 105 ms warten, bevor es tatsächlich ausgeführt wird.
ausstehende Rückrufe
In dieser Phase werden einige Systemrückrufvorgänge ausgeführt. Wenn beispielsweise eine TCP-Verbindung hergestellt wird, empfängt der TCP-Socket in einigen Liunx-Betriebssystemen diesen Fehler wird in ausstehenden Rückrufen ausgeführt.
Oder die E/A-Rückrufoperation, die in der nächsten Ereignisschleife ausgeführt werden muss.
Leerlauf, Vorbereiten
Leerlauf, Vorbereiten ist die Phase, die intern verwendet wird, daher werde ich sie hier nicht im Detail vorstellen.
poll polling
poll erkennt neue I/O-Ereignisse und führt I/O-bezogene Rückrufe aus. Beachten Sie, dass sich die Rückrufe hier auf fast alles beziehen, außer auf schließende Rückrufe, Timer und setImmediate-Rückrufereignisse.
poll kümmert sich hauptsächlich um zwei Dinge: E/A-Abfragen, Berechnen der Blockzeit und anschließendes Verarbeiten von Ereignissen in der Abfragewarteschlange.
Wenn die Abfragewarteschlange nicht leer ist, durchläuft die Ereignisschleife die Rückrufe in der Warteschlange und führt sie dann synchron nacheinander aus, bis die Warteschlange belegt ist oder das Limit für die Anzahl der Rückrufe erreicht ist.
Da die Rückrufe in der Warteschlange synchron nacheinander ausgeführt werden, kann es zu Blockierungen kommen.
Wenn die Umfragewarteschlange leer ist und setImmediate im Code aufgerufen wird, springt er sofort zur nächsten Prüfphase und führt dann den Rückruf in setImmediate aus. Wenn setImmediate nicht aufgerufen wird, wartet es weiterhin darauf, dass der neue Rückruf zur Warteschlange hinzugefügt und ausgeführt wird.
check
wird hauptsächlich zum Ausführen des Rückrufs von setImmediate verwendet.
setImmediate kann als einzigartiger Timer betrachtet werden, der in einer separaten Phase läuft, und die zugrunde liegende libuv-API wird zum Planen von Rückrufen verwendet.
Wenn es in der Umfragephase einen Rückruf namens setImmediate gibt, wird die Umfragephase im Allgemeinen sofort beendet, wenn die Umfragewarteschlange leer ist, und die Prüfphase wird eingegeben, um die entsprechende Rückrufmethode auszuführen.
Rückrufe schließen
Die letzte Phase besteht darin, die Rückrufe im Abschlussereignis zu verarbeiten. Wenn beispielsweise ein Socket plötzlich geschlossen wird, wird ein Schließereignis ausgelöst und der zugehörige Rückruf aufgerufen.
setTimeout和setImmediate有什么不同呢?
从上图的phase阶段可以看出,setTimeout中的callback是在timer phase中执行的,而setImmediate是在check阶段执行的。
从语义上讲,setTimeout指的是,在给定的时间之后运行某个callback。而setImmediate是在执行完当前loop中的 I/O操作之后,立马执行。
那么这两个方法的执行顺序上有什么区别呢?
下面我们举两个例子,第一个例子中两个方法都是在主模块中运行:
setTimeout(() => { console.log('timeout'); }, 0); setImmediate(() => { console.log('immediate'); });
这样运行两个方法的执行顺序是不确定,因为可能受到其他执行程序的影响。
第二个例子是在I/O模块中运行这两个方法:
const fs = require('fs'); fs.readFile(__filename, () => { setTimeout(() => { console.log('timeout'); }, 0); setImmediate(() => { console.log('immediate'); }); });
你会发现,在I/O模块中,setImmediate一定会在setTimeout之前执行。
两者的共同点
setTimeout和setImmediate两者都有一个返回值,我们可以通过这个返回值,来对timer进行clear操作:
const timeoutObj = setTimeout(() => { console.log('timeout beyond time'); }, 1500); const immediateObj = setImmediate(() => { console.log('immediately executing immediate'); }); const intervalObj = setInterval(() => { console.log('interviewing the interval'); }, 500); clearTimeout(timeoutObj); clearImmediate(immediateObj); clearInterval(intervalObj);
clear操作也可以clear intervalObj。
unref 和 ref
setTimeout和setInterval返回的对象都是Timeout对象。
如果这个timeout对象是最后要执行的timeout对象,那么可以使用unref方法来取消其执行,取消执行完毕,可以使用ref来恢复它的执行。
const timerObj = setTimeout(() => { console.log('will i run?'); }); timerObj.unref(); setImmediate(() => { timerObj.ref(); });
注意,如果有多个timeout对象,只有最后一个timeout对象的unref方法才会生效。
process.nextTick也是一种异步API,但是它和timer是不同的。
如果我们在一个phase中调用process.nextTick,那么nextTick中的callback会在这个phase完成,进入event loop的下一个phase之前完成。
这样做就会有一个问题,如果我们在process.nextTick中进行递归调用的话,这个phase将会被阻塞,影响event loop的正常执行。
那么,为什么我们还会有process.nextTick呢?
考虑下面的一个例子:
let bar; function someAsyncApiCall(callback) { callback(); } someAsyncApiCall(() => { console.log('bar', bar); // undefined }); bar = 1;
上面的例子中,我们定义了一个someAsyncApiCall方法,里面执行了传入的callback函数。
这个callback函数想要输出bar的值,但是bar的值是在someAsyncApiCall方法之后被赋值的。
这个例子最终会导致输出的bar值是undefined。
我们的本意是想让用户程序执行完毕之后,再调用callback,那么我们可以使用process.nextTick来对上面的例子进行改写:
let bar; function someAsyncApiCall(callback) { process.nextTick(callback); } someAsyncApiCall(() => { console.log('bar', bar); // 1 }); bar = 1;
我们再看一个实际中使用的例子:
const server = net.createServer(() => {}).listen(8080); server.on('listening', () => {});
上面的例子是最简单的nodejs创建web服务。
上面的例子有什么问题呢?listen(8000) 方法将会立马绑定8000端口。但是这个时候,server的listening事件绑定代码还没有执行。
这里实际上就用到了process.nextTick技术,从而不管我们在什么地方绑定listening事件,都可以监听到listen事件。
process.nextTick 和 setImmediate 的区别
process.nextTick 是立马在当前phase执行callback,而setImmediate是在check阶段执行callback。
所以process.nextTick要比setImmediate的执行顺序优先。
实际上,process.nextTick和setImmediate的语义应该进行互换。因为process.nextTick表示的才是immediate,而setImmediate表示的是next tick。
本文作者:flydean程序那些事
本文链接:http://www.flydean.com/nodejs-event-more/
本文来源:flydean的博客
更多编程相关知识,请访问:编程视频!!
Das obige ist der detaillierte Inhalt vonEine eingehende Analyse von Ereignissen und Ereignisschleifen in NodeJS. Für weitere Informationen folgen Sie bitte anderen verwandten Artikeln auf der PHP chinesischen Website!