Heim  >  Artikel  >  Web-Frontend  >  Detaillierte Erläuterung der Node.js-Ereignisschleife (Event Loop) und des Threads pool_node.js

Detaillierte Erläuterung der Node.js-Ereignisschleife (Event Loop) und des Threads pool_node.js

WBOY
WBOYOriginal
2016-05-16 16:17:231197Durchsuche

Die „Ereignisschleife“ des Knotens ist der Kern seiner Fähigkeit, große Parallelität und hohen Durchsatz zu bewältigen. Dies ist der magischste Teil, wonach Node.js grundsätzlich als „Single-Threaded“ verstanden werden kann und gleichzeitig die Verarbeitung beliebiger Vorgänge im Hintergrund ermöglicht. In diesem Artikel wird erläutert, wie die Ereignisschleife funktioniert, damit Sie ihre Magie spüren können.

Ereignisgesteuerte Programmierung

Um die Ereignisschleife zu verstehen, müssen Sie zunächst die ereignisgesteuerte Programmierung verstehen. Es erschien 1960. Heutzutage wird die ereignisgesteuerte Programmierung häufig in der UI-Programmierung verwendet. Eine der Hauptanwendungen von JavaScript ist die Interaktion mit dem DOM, daher ist die Verwendung einer ereignisbasierten API selbstverständlich.

Einfach definiert: Ereignisgesteuerte Programmierung steuert den Ablauf einer Anwendung durch Ereignisse oder Zustandsänderungen. Wird im Allgemeinen durch Ereignisüberwachung implementiert. Sobald das Ereignis erkannt wird (dh sich der Status ändert), wird die entsprechende Rückruffunktion aufgerufen. Kommt Ihnen das bekannt vor? Tatsächlich ist dies das grundlegende Funktionsprinzip der Node.js-Ereignisschleife.

Wenn Sie mit der clientseitigen JavaScript-Entwicklung vertraut sind, denken Sie an die .on*()-Methoden wie element.onclick(), die zur Kombination mit DOM-Elementen verwendet werden, um Benutzerinteraktion bereitzustellen. Dieser Arbeitsmodus ermöglicht das Auslösen mehrerer Ereignisse auf einer einzelnen Instanz. Node.js löst dieses Muster durch EventEmitter (Ereignisgeneratoren) aus, beispielsweise in den serverseitigen Socket- und „http“-Modulen. Eine oder mehrere Zustandsänderungen können von einer einzelnen Instanz aus ausgelöst werden.

Ein weiteres häufiges Muster besteht darin, Erfolg und Misserfolg auszudrücken. Im Allgemeinen gibt es zwei gängige Implementierungsmethoden. Die erste besteht darin, die „Fehlerausnahme“ an den Rückruf zu übergeben, normalerweise als ersten Parameter an die Rückruffunktion. Der zweite verwendet das Promises-Entwurfsmuster und hat ES6 hinzugefügt. Hinweis* Der Promise-Modus verwendet eine Methode zum Schreiben von Funktionsketten ähnlich wie jQuery, um eine tiefe Verschachtelung von Rückruffunktionen zu vermeiden, wie zum Beispiel:

Code kopieren Der Code lautet wie folgt:

$.getJSON('/getUser').done(successHandler).fail(failHandler)

Das Modul „fs“ (Dateisystem) übernimmt größtenteils den Stil der Übergabe von Ausnahmen an Rückrufe. Technisch gesehen löst es bestimmte Aufrufe aus, beispielsweise das angehängte Ereignis fs.readFile(), aber die API dient lediglich dazu, den Benutzer zu warnen und den Erfolg oder Misserfolg des Vorgangs auszudrücken. Die Wahl einer solchen API basiert eher auf architektonischen Überlegungen als auf technischen Einschränkungen.

Ein häufiges Missverständnis besteht darin, dass Ereignisemitter beim Auslösen von Ereignissen ebenfalls von Natur aus asynchron sind. Dies ist jedoch falsch. Unten finden Sie einen einfachen Codeausschnitt, um dies zu demonstrieren.

Code kopieren Der Code lautet wie folgt:

Funktion MyEmitter() {
EventEmitter.call(this);
}
util.inherits(MyEmitter, EventEmitter);

MyEmitter.prototype.doStuff = function doStuff() {
console.log('before')
emitter.emit('fire')
console.log('after')}
};

var me = new MyEmitter();
me.on('fire', function() {
console.log('emit fired');
});

me.doStuff();
// Ausgabe:
// vorher
// emitted fired
// danach

Hinweis* Wenn emitter.emit asynchron ist, sollte die Ausgabe
sein // vorher
// danach
// emitted fired


EventEmitter verhält sich häufig asynchron, da es häufig zum Benachrichtigen von Vorgängen verwendet wird, die asynchron abgeschlossen werden müssen. Die EventEmitter-API selbst ist jedoch vollständig synchron. Abhörfunktionen können intern asynchron ausgeführt werden. Beachten Sie jedoch, dass alle Abhörfunktionen in der Reihenfolge, in der sie hinzugefügt werden, synchron ausgeführt werden.

Mechanismusübersicht und Thread-Pool

Der Knoten selbst ist auf mehrere Bibliotheken angewiesen. Eine davon ist libuv, die erstaunliche Bibliothek für die Handhabung asynchroner Ereigniswarteschlangen und deren Ausführung.

Knoten nutzt so viel wie möglich vom Betriebssystemkernel, um vorhandene Funktionen zu implementieren. Zum Beispiel Antwortanfragen generieren, Verbindungen weiterleiten und diese dem System zur Verarbeitung anvertrauen. Beispielsweise werden eingehende Verbindungen über das Betriebssystem in die Warteschlange gestellt, bis sie von Node verarbeitet werden können.

Sie haben vielleicht gehört, dass Node über einen Thread-Pool verfügt, und fragen sich vielleicht: „Wenn Node Aufgaben der Reihe nach verarbeitet, warum brauchen wir dann einen Thread-Pool?“ Dies liegt daran, dass im Kernel nicht alle Aufgaben verarbeitet werden Auftrag. Wird asynchron ausgeführt. In diesem Fall muss Node.JS in der Lage sein, den Thread während des Betriebs für eine bestimmte Zeitspanne zu sperren, damit er die Ereignisschleife weiterhin ausführen kann, ohne blockiert zu werden.

Das Folgende ist ein einfaches Beispieldiagramm, das den internen Betriebsmechanismus zeigt:


┌──────────────────────┐
╭──►│ Timer Timer │        └───────────┬───────────┘
│       ┌───────────┴───────────┐
│ Ausstehende Rückrufe │          └──────────┬────────────┘                                                                                                                                  | │ │ │ UMFRAGE ││── Verbindungen, │
│                                                                                                                                                                                                                                              seitdem │           ┌───────────┴───────────┐                                                                                                                                       ╰─── ┤ setImmediate └───────────────────────┘

Es gibt einige Dinge, die an der internen Funktionsweise der Ereignisschleife schwer zu verstehen sind:

Alle Rückrufe werden über process.nextTick() am Ende einer Phase der Ereignisschleife (z. B. Timer) und vor dem Übergang zur nächsten Phase voreingestellt. Dadurch werden potenzielle rekursive Aufrufe von process.nextTick() vermieden, die eine Endlosschleife verursachen.
„Ausstehende Rückrufe“ sind Rückrufe in der Rückrufwarteschlange, die von keinem anderen Ereignisschleifenzyklus verarbeitet werden (z. B. an fs.write übergeben).

Ereignissemitter und Ereignisschleife

Vereinfachen Sie die Interaktion mit der Ereignisschleife, indem Sie einen EventEmitter erstellen. Es handelt sich um einen generischen Wrapper, mit dem Sie ereignisbasierte APIs einfacher erstellen können. Wie die beiden interagieren, verwirrt Entwickler oft.

Das folgende Beispiel zeigt, dass das Vergessen, dass ein Ereignis synchron ausgelöst wird, dazu führen kann, dass das Ereignis verpasst wird.

Code kopieren Der Code lautet wie folgt:

// Nach v0.10 wird require('events').EventEmitter nicht mehr benötigt
var EventEmitter = require('events');
var util = require('util');

Funktion MyThing() {
EventEmitter.call(this);

doFirstThing();
this.emit('thing1');
}
util.inherits(MyThing, EventEmitter);

var mt = new MyThing();

mt.on('thing1', function onThing1() {
// Entschuldigung, dieses Ereignis wird nie stattfinden
});


Das obige Ereignis „thing1“ wird niemals von MyThing() erfasst, da MyThing() instanziiert werden muss, bevor es auf Ereignisse warten kann. Hier ist eine einfache Problemumgehung, ohne zusätzliche Verschlüsse hinzuzufügen:
Code kopieren Der Code lautet wie folgt:

var EventEmitter = require('events');
var util = require('util');

Funktion MyThing() {
EventEmitter.call(this);

doFirstThing();
setImmediate(emitThing1, this);
}
util.inherits(MyThing, EventEmitter);

Funktion emitThing1(self) {
self.emit('thing1');
}

var mt = new MyThing();

mt.on('thing1', function onThing1() {
// Ausgeführt
});

Die folgende Lösung funktioniert auch, allerdings auf Kosten der Leistung:

Code kopieren Der Code lautet wie folgt:

Funktion MyThing() {
EventEmitter.call(this);

doFirstThing();
// Die Verwendung von Function#bind() führt zu Leistungseinbußen
setImmediate(this.emit.bind(this, 'thing1'));
}
util.inherits(MyThing, EventEmitter);


Ein weiteres Problem ist das Auslösen eines Fehlers. Das Auffinden von Problemen in Ihrer Anwendung ist schon schwer genug, aber ohne den Aufrufstapel (Hinweis *e.stack) ist das Debuggen nahezu unmöglich. Wenn von einer asynchronen Remote-Anfrage ein Fehler empfangen wird, geht der Aufrufstapel verloren. Es gibt zwei mögliche Lösungen: synchron auslösen oder sicherstellen, dass der Fehler zusammen mit anderen wichtigen Informationen übergeben wird. Die folgenden Beispiele veranschaulichen beide Lösungen:
Code kopieren Der Code lautet wie folgt:

MyThing.prototype.foo = Funktion foo() {
// Dieser Fehler wird asynchron ausgelöst
var er = doFirstThing();
if (er) {
//Bei Auslösung muss ein neuer Fehler erstellt werden, um die Aufrufstapelinformationen vor Ort beizubehalten
setImmediate(emitError, this, new Error('Bad stuff'));
Zurück;
}

// Fehler auslösen und sofort (synchron) behandeln
var er = doSecondThing();
if (er) {
This.emit('error', 'More bad stuff');
Zurück;
}
}


Bewerten Sie die Situation. Wenn ein Fehler auftritt, kann dieser sofort behandelt werden. Oder es könnte sich um eine triviale Ausnahme handeln, die leicht gehandhabt oder später behoben werden kann. Darüber hinaus ist es keine gute Idee, Error über einen Konstruktor zu übergeben, da die erstellte Objektinstanz wahrscheinlich unvollständig ist. Eine Ausnahme stellt der Fall dar, in dem gerade direkt ein Fehler ausgelöst wurde.

Fazit

In diesem Artikel werden kurz das Innenleben und die technischen Details der Ereignisschleife erläutert. Es ist alles gut durchdacht. Ein weiterer Artikel befasst sich mit der Interaktion der Ereignisschleife mit dem Systemkernel und zeigt die Magie des asynchronen NodeJS-Betriebs.

Stellungnahme:
Der Inhalt dieses Artikels wird freiwillig von Internetnutzern beigesteuert und das Urheberrecht liegt beim ursprünglichen Autor. Diese Website übernimmt keine entsprechende rechtliche Verantwortung. Wenn Sie Inhalte finden, bei denen der Verdacht eines Plagiats oder einer Rechtsverletzung besteht, wenden Sie sich bitte an admin@php.cn