In herkömmlichen Programmiermodulen sind E/A -Operationen wie ein normaler lokaler Funktionsaufruf: Das Programm wird blockiert, bevor die Funktion ausgeführt wird und kann nicht weiter ausgeführt werden. Blockiertes E/O stammt aus dem früheren Zeit -Slice -Modell, bei dem jeder Prozess wie eine unabhängige Person ist, um alle zu unterscheiden, und jeder kann normalerweise nur eine Sache gleichzeitig tun und muss darauf warten, dass die vorherige Sache erledigt wird, bevor er entscheidet, was als nächstes zu tun ist. Dieses Modell von "einem Benutzer, einem Prozess", der in Computernetzwerken weit verbreitet ist und das Internet sehr skalierbar ist. Bei der Verwaltung mehrerer Prozesse konsumiert es viel Speicher und Kontextumschaltungen werden auch viele Ressourcen einnehmen. Dies ist eine enorme Belastung für das Betriebssystem, und mit zunehmender Anzahl von Prozessen wird die Systemleistung stark zerfallen.
Multithreading ist eine Alternative. Ein Thread ist ein leichter Vorgang, der den Speicher mit anderen Threads im selben Prozess teilt. Es ist eher eine Erweiterung des traditionellen Modells, mit dem mehrere Threads gleichzeitig ausgeführt werden. Wenn ein Thread auf E/A -Operationen wartet, können andere Threads die CPU übernehmen. Wenn die E/A -Operation abgeschlossen ist, wird der vorne wartende Faden erweckt. Das heißt, ein laufender Thread kann unterbrochen und später wieder aufgenommen werden. Darüber hinaus können Fäden unter verschiedenen Kernen von Multi-Core-CPUs unter einigen Systemen parallel ausgeführt werden.
Programmierer wissen nicht, wie spät der Thread ausgeführt wird. Sie müssen darauf achten, den gleichzeitigen Zugriff auf gemeinsam genutzten Speicher zu verarbeiten. Sie müssen daher einige Synchronisation -Primitive verwenden, um den Zugriff auf eine bestimmte Datenstruktur zu synchronisieren, z. Anwendungen, die stark auf den gemeinsamen Zustand zwischen Threads angewiesen sind, können leicht einige seltsame Probleme mit starker Zufälligkeit und Schwierigkeiten bei der Suche haben.
Eine andere Möglichkeit besteht darin, die Zusammenarbeit mit mehreren Threads zu verwenden, wo Sie dafür verantwortlich sind, die CPU explizit freizugeben und die CPU-Zeit an andere Threads zu übergeben. Da Sie den Ausführungsplan des Threads persönlich steuern, wird die Notwendigkeit einer Synchronisation verringert, erhöht jedoch auch die Komplexität des Programms und die Wahrscheinlichkeit von Fehlern und vermeidet die Probleme des Multi-Threading nicht.
Was ist eine ereignisgesteuerte Programmierung
Die ereignisgesteuerte Programmierung ist ein Programmierstil, bei dem Ereignisse den Ausführungsprozess eines Programms bestimmen. Die Veranstaltungen werden von Event -Handlern oder Ereignisrückrufen behandelt. Ereignisrückrufe sind Funktionen, die auf dem Auftritt eines bestimmten Ereignisses aufgelegt werden, z. B. die Datenbank gibt das Abfrageergebnis zurück oder der Benutzer klickt auf eine Schaltfläche.
Denken Sie daran, dass Datenbankabfragen im traditionellen blockierten E/A -Programmiermodus so aussehen können:
Die Codekopie lautet wie folgt:
result = query ('select * aus posts wobei id = 1');
Do_Something_With (Ergebnis);
Die obige Abfragefunktion hält den aktuellen Thread oder den Prozess in einem Wartezustand, bis die zugrunde liegende Datenbank den Abfragevorgang abgeschlossen und zurückgibt.
In dem ereignisgesteuerten Modell wird diese Abfrage wie folgt:
Die Codekopie lautet wie folgt:
query_finished = function (result) {
Do_Something_With (Ergebnis);
}
Abfrage ('Auswählen * aus Beiträgen, wobei ID = 1', query_finished);
Zuerst definieren Sie eine Funktion namens query_finished, die enthält, was nach Abschluss der Abfrage zu tun ist. Geben Sie diese Funktion dann als Parameter an die Abfragefunktion weiter. Query_finished wird nach der Abfrageausführung aufgerufen, anstatt nur das Abfrageergebnis zurückzugeben.
Wenn ein Ereignis, an dem Sie interessiert sind, auftritt, wird die von Ihnen definierte Funktion aufgerufen, anstatt einfach den Ergebniswert zurückzugeben. Dieses Programmiermodell wird als ereignisgesteuerte Programmierung oder asynchrone Programmierung bezeichnet. Dies ist eines der offensichtlichsten Merkmale des Knotens. Dieses Programmiermodell bedeutet, dass der aktuelle Prozess bei der Ausführung von E/A -Operationen nicht blockiert wird. Daher können mehrere E/A -Operationen parallel ausgeführt werden, und die entsprechende Rückruffunktion wird nach Abschluss des Vorgangs aufgerufen.
Die zugrunde liegende Ebene ereignisgesteuerter Programmierung basiert auf Ereignisschleifen. Ereignisschleifen sind im Grunde eine Struktur, in der Ereigniserkennung und Ereignisverfahren den kontinuierlichen Schleifenaufruf dieser beiden Funktionen auslösen. In jeder Schleife muss der Ereignisschleifmechanismus erkennen, welche Ereignisse aufgetreten sind. Wenn das Ereignis auftritt, findet es die entsprechende Rückruffunktion und ruft sie auf.
Die Ereignisschleife ist nur ein Thread, der dabei ausgeführt wird. Wenn ein Ereignis auftritt, kann der Ereignisprozessor alleine laufen und wird nicht unterbrochen, das heißt:
1. Die Callback -Funktion des Ereignisses wird höchstens zu einem bestimmten Zeitpunkt ausgeführt
2. Beim Laufen wird kein Ereignisverfahren unterbrochen
Damit können Entwickler keine Kopfschmerzen über die Synchronisation der Thread und die gleichzeitige Änderung des gemeinsamen Speichers mehr haben.
Ein bekanntes Geheimnis:
Vor langer Zeit wussten die Leute in der Systemprogrammiergemeinschaft, dass eine ereignisgesteuerte Programmierung der beste Weg war, um hohe Parallelitätsdienste zu schaffen, da sie nicht viel Kontext sparen musste, sodass es viel Speicher speichert hatte, nicht so viel Kontextschalter und viel Ausführungszeit.
Langsam durchdrang dieses Konzept andere Plattformen und Gemeinschaften, und einige berühmte Implementierungen für Event -Loop -Implementierungen wie Rubys Event -Maschine, Perls Anyevnet und Python's Twisted. Darüber hinaus gibt es viele andere Implementierungen und Sprachen.
Um diese Frameworks zu entwickeln, müssen Sie spezifische Kenntnisse im Zusammenhang mit dem Framework- und Framework-spezifischen Klassenbibliotheken erlernen. Wenn Sie beispielsweise die Ereignismaschine verwenden, müssen Sie die Verwendung von Synchronklassenbibliotheken vermeiden, um die Vorteile von Nichtblocking zu genießen, und können nur die asynchronen Klassenbibliotheken der Event-Maschine verwenden. Wenn Sie eine blockierende Bibliothek (z. B. die meisten Ruby -Standardbibliothek) verwenden, verliert Ihr Server seine optimale Skalierbarkeit, da die Ereignisschleife weiterhin ständig blockiert wird, wodurch die Verarbeitung von E/A -Ereignissen von Zeit zu Zeit blockiert wird.
Der Knoten wurde ursprünglich als nicht blockierende E/A-Serverplattform entworfen. Im Allgemeinen sollten Sie im Allgemeinen erwarten, dass der gesamte Code nicht blockiert wird. Da JavaScript sehr klein ist und kein E/A -Modell erzwingt (da es keine Standard -E/A -Klassenbibliothek hat), ist der Knoten in einer sehr reinen Umgebung eingebaut und es wird keine Legacy -Probleme geben.
Wie Knoten und JavaScript asynchrone Anwendungen vereinfachen
Der Autor von Node, Ryan Dahl, nutzte zunächst C, um dieses Projekt zu entwickeln, stellte jedoch fest, dass der Kontext der Aufrechterhaltung von Funktionsaufrufen zu komplex war, was zu einer hohen Codekomplexität führte. Dann wechselte er zu Lua, aber Lua hat bereits mehrere blockierende E/A -Bibliotheken. Das Mischen von Blockieren und Nichtblockieren kann Entwickler verwirren und so viele Menschen daran hindern, skalierbare Anwendungen aufzubauen. Daher wurde Lua auch von Dahl aufgegeben. Schließlich wandte er sich an JavaScript, Schließungen in JavaScript und Funktionen von Objekten der ersten Ebene, die JavaScript für ereignisgesteuerte Programme sehr geeignet machen. Die Magie von JavaScript ist einer der Hauptgründe, warum Knoten so beliebt ist.
Was ist eine Schließung?
Ein Verschluss kann als besondere Funktion verstanden werden, kann jedoch Variablen im von ihr definierten Bereich erben und zugreifen. Wenn Sie eine Rückruffunktion als Parameter an eine andere Funktion übergeben, wird sie später aufgerufen. Die Magie ist, dass diese Rückruffunktion, die später aufgerufen wird, tatsächlich an den Kontext erinnert, in dem sie sich selbst und die Variablen im übergeordneten Kontext definiert, und kann auch normal auf sie zugreifen. Diese leistungsstarke Funktion ist der Kern des Erfolgs des Knotens.
Das folgende Beispiel zeigt, wie JavaScript -Schließungen in einem Webbrowser funktionieren. Wenn Sie auf einer Schaltfläche auf ein eigenständiges Ereignis zuhören möchten, können Sie dies tun:
Die Codekopie lautet wie folgt:
var clickCount = 0;
document.getElementById ('myButton'). onclick = function () {
ClickCount += 1;
alert ("geklickt" + clickCount + "Zeiten");
};
So, wie bei der Verwendung von JQuery:
Die Codekopie lautet wie folgt:
var clickCount = 0;
$ ('Button#myButton'). Click (function () {
ClickedCount ++;
alert ('klickte' + clickCount + 'mal.');
});
In JavaScript sind Funktionen die erste Art von Objekten, was bedeutet, dass Sie Funktionen als Parameter an andere Funktionen übergeben können. In den beiden oben genannten Beispielen weist er erstere einer anderen Funktion eine Funktion zu, und der letztere übergibt die Funktion als Parameter an eine andere Funktion. Die Funktion zur Ereignisverarbeitungsfunktion (Rückruffunktion) kann auf jede Variable im Codeblock zugreifen, in der die Funktion sie definiert. In diesem Beispiel kann es auf die in seiner Elternverschluss definierte ClickCount -Variable zugreifen.
Die ClickCount -Variable befindet sich im globalen Bereich (dem äußersten Bereich in JavaScript), der die Anzahl spart, mit der der Benutzer auf eine Schaltfläche klickt. In der Regel ist es eine schlechte Angewohnheit, Variablen im globalen Bereich zu speichern, da es einfach ist, mit einem anderen Code in Konflikt zu geraten, und Sie sollten Variablen in den lokalen Bereich aufnehmen, in dem Sie sie verwenden. Die meiste Zeit ist das Einwickeln des Codes mit einer Funktion gleichbedeutend mit einer weiteren Schließung, was leicht vermeiden kann, die globale Umgebung zu verschmutzen, genau wie diese:
Die Codekopie lautet wie folgt:
(function () {
var clickCount = 0;
$ ('Button#myButton'). Click (function () {
ClickCount ++;
alert ('klickte' + clickCount + 'mal.');
});
} ());
HINWEIS: Die siebte Zeile des obigen Codes definiert eine Funktion und ruft sie sofort auf. Dies ist ein gemeinsames Designmuster in JavaScript: Erstellen Sie einen neuen Bereich, indem Sie eine Funktion erstellen.
Wie Schließungen die asynchrone Programmierung helfen
Schreiben Sie im ereignisgesteuerten Programmiermodell zuerst den Code, um nach dem Ereignis auszuführen, dann den Code in eine Funktion einzulegen und die Funktion schließlich als Parameter an den Anrufer weiterzugeben und ihn dann von der Anruferfunktion aufzurufen.
In JavaScript ist eine Funktion keine isolierte Definition. Es erinnert sich auch an den Kontext des von ihm deklarierten Bereichs. Mit diesem Mechanismus können JavaScript -Funktionen auf den Kontext zugreifen, in dem sich die Funktionsdefinition befindet, und alle Variablen im übergeordneten Kontext.
Wenn Sie eine Rückruffunktion als Parameter an den Anrufer übergeben, wird die Funktion zu einem späteren Zeitpunkt aufgerufen. Selbst wenn der Umfang, der die Rückruffunktion definiert, beendet ist und die Rückruffunktion aufgerufen wird, kann er dennoch auf alle Variablen im beendeten Bereich und in seinem übergeordneten Bereich zugreifen. Wie beim letzten Beispiel wird die Rückruffunktion in der Click () von JQuery aufgerufen, kann jedoch weiterhin auf die ClickCount -Variable zugreifen.
Die Magie der Schließungen wird früher gezeigt. Durch das Übergeben von Zustandsvariablen an eine Funktion können Sie eine ereignisgesteuerte Programmierung ohne Aufrechterhaltung von Zuständen durchführen. Der Schließmechanismus von JavaScript hilft Ihnen dabei, sie beizubehalten.
Zusammenfassung
Die ereignisgesteuerte Programmierung ist ein Programmiermodell, das den Programmausführungsprozess durch Ereignisauslösen bestimmt. Programmierer registrieren Rückruffunktionen für Ereignisse, an denen sie interessiert sind (normalerweise als Ereignishandler genannt), und das System ruft dann den registrierten Ereignishandler auf, wenn das Ereignis auftritt. Dieses Programmiermodell hat viele Vorteile, die traditionelle Blockierungsprogrammiermodelle nicht haben. Um ähnliche Funktionen zu implementieren, muss in der Vergangenheit Multi-Process/Multi-Threading verwendet werden.
JavaScript ist eine leistungsstarke Sprache, da die Funktions- und Verschlusseigenschaften des Objekts und Verschlussseitungen für die ereignisgesteuerte Programmierung sehr geeignet sind.