Heim  >  Artikel  >  Web-Frontend  >  Detaillierte Erklärung der Ereignisschleife in JS und Node.js

Detaillierte Erklärung der Ereignisschleife in JS und Node.js

小云云
小云云Original
2017-12-13 09:30:471505Durchsuche

Das

in event loopjs führt zu dem Problem unterschiedlicher Ausführungsergebnisse beim Ausführen von Programmen mit setTimeout und Promise in Chrome und Node, was zum event loop-Mechanismus von Nodejs führt Ich habe Ihnen eine detaillierte Analyse der Prinzipien und der Verwendung von Ereignissen in JS und Node.js gegeben. Ich hoffe, dass es Ihnen helfen kann.

console.log(1)
setTimeout(function() {
 new Promise(function(resolve, reject) {
 console.log(2)
 resolve()
 })
 .then(() => {
 console.log(3)
 })
}, 0)
setTimeout(function() {
 console.log(4)
}, 0)
// chrome中运行:1 2 3 4
// Node中运行: 1 2 4 3

Die Ausführungsergebnisse von Chrome und Node sind unterschiedlich, was sehr interessant ist.

1. Aufgabenwarteschlange in JS

Ein Hauptmerkmal der JavaScript-Sprache ist Single Thread, das heißt, nur eine Sache ausführen. Warum kann JavaScript also nicht mehrere Threads haben? Dadurch kann die Effizienz verbessert werden.

Der einzelne Thread von JavaScript hängt mit seinem Zweck zusammen. Als Browser-Skriptsprache besteht der Hauptzweck von JavaScript darin, mit Benutzern zu interagieren und das DOM zu manipulieren. Dies legt fest, dass es nur Single-Threaded sein kann, da es sonst zu sehr komplexen Synchronisationsproblemen kommt. Angenommen, JavaScript verfügt über zwei Threads gleichzeitig. Ein Thread fügt Inhalt zu einem bestimmten DOM-Knoten hinzu und der andere Thread löscht den Knoten. Welchen Thread sollte der Browser in diesem Fall verwenden?

Um Komplexität zu vermeiden, ist JavaScript seit seiner Geburt Single-Threaded. Dies ist zum Kernmerkmal dieser Sprache geworden und wird sich auch in Zukunft nicht ändern.

Um die Rechenleistung von Multi-Core-CPUs zu nutzen, schlägt HTML5 den Web Worker-Standard vor, der es JavaScript-Skripten ermöglicht, mehrere Threads zu erstellen, die untergeordneten Threads werden jedoch vollständig vom Hauptthread gesteuert Thread und darf das DOM nicht bedienen. Daher ändert dieser neue Standard nichts an der Single-Threaded-Natur von JavaScript.

2. Aufgabenwarteschlangen-Ereignisschleife

Einzelner Thread bedeutet, dass alle Aufgaben in die Warteschlange gestellt werden müssen und die vorherige Aufgabe erst ausgeführt wird Es endet eine Mission. Wenn die vorherige Aufgabe lange dauert, muss die nächste Aufgabe warten.

Daher können alle Aufgaben in zwei Typen unterteilt werden, einer ist eine synchrone Aufgabe (synchron) und der andere ist eine asynchrone Aufgabe (asynchron). Synchrone Aufgaben beziehen sich auf Aufgaben, die zur Ausführung im Hauptthread in die Warteschlange gestellt werden. Die nächste Aufgabe kann erst ausgeführt werden, nachdem die vorherige Aufgabe ausgeführt wurde. Erst wenn die „Aufgabenwarteschlange“ dem Hauptthread mitteilt, dass eine asynchrone Aufgabe ausgeführt werden kann, gelangt die Aufgabe zur Ausführung in den Hauptthread.

Im Einzelnen ist der Betriebsmechanismus der asynchronen Ausführung wie folgt. (Dasselbe gilt für die synchrone Ausführung, da sie als asynchrone Ausführung ohne asynchrone Aufgaben betrachtet werden kann.)

Alle synchronen Aufgaben werden im Hauptthread ausgeführt und bilden einen Ausführungskontextstapel. Zusätzlich zum Hauptthread gibt es auch eine „Aufgabenwarteschlange“. Solange die asynchrone Aufgabe laufende Ergebnisse hat, wird ein Ereignis in die „Aufgabenwarteschlange“ gestellt. Sobald alle Synchronisierungsaufgaben im „Ausführungsstapel“ ausgeführt wurden, liest das System die „Aufgabenwarteschlange“, um zu sehen, welche Ereignisse sich darin befinden. Diese entsprechenden asynchronen Aufgaben beenden den Wartezustand, betreten den Ausführungsstapel und beginnen mit der Ausführung. Der Hauptthread wiederholt den dritten Schritt oben immer wieder.

Solange der Hauptthread leer ist, wird die „Aufgabenwarteschlange“ gelesen. Dies ist der laufende Mechanismus von JavaScript. Dieser Vorgang wiederholt sich ständig.

3. Timer setTimeout und setInterval

Die Timerfunktion besteht hauptsächlich aus den Funktionen setTimeout() und setInterval() Die internen Betriebsmechanismen sind genau gleich. Der Unterschied besteht darin, dass der von erstere angegebene Code einmal ausgeführt wird, während letzterer wiederholt ausgeführt wird.

setTimeout(fn,0) bedeutet, eine Aufgabe anzugeben, die in der frühesten verfügbaren Leerlaufzeit des Hauptthreads ausgeführt werden soll, dh so früh wie möglich ausgeführt werden soll. Es fügt am Ende der „Aufgabenwarteschlange“ ein Ereignis hinzu, sodass es erst ausgeführt wird, wenn die Synchronisierungsaufgabe und die vorhandenen Ereignisse in der „Aufgabenwarteschlange“ verarbeitet wurden. Der

HTML5-Standard gibt den Mindestwert (kürzestes Intervall) des zweiten Parameters von setTimeout() an, der nicht weniger als 4 Millisekunden betragen darf. Wenn er kleiner als dieser Wert ist, erfolgt dies automatisch Zunahme. Zuvor haben ältere Browser das Mindestintervall auf 10 Millisekunden eingestellt. Darüber hinaus werden diese DOM-Änderungen (insbesondere solche, die das erneute Rendern von Seiten beinhalten) normalerweise nicht sofort, sondern alle 16 Millisekunden ausgeführt. Zu diesem Zeitpunkt ist die Wirkung von requestAnimationFrame() besser als setTimeout().

Es ist zu beachten, dass setTimeout() das Ereignis nur in die „Aufgabenwarteschlange“ einfügt. Der Hauptthread muss warten, bis die Ausführung des aktuellen Codes (Ausführungsstapels) abgeschlossen ist, bevor der Hauptthread die Rückruffunktion ausführt gibt an. Wenn der aktuelle Code lange dauert, kann es lange dauern, sodass nicht garantiert werden kann, dass die Rückruffunktion zu dem von setTimeout () angegebenen Zeitpunkt ausgeführt wird.

4. Ereignisschleife von Node.js

Ereignisabfrage fragt hauptsächlich die Ereigniswarteschlange ab und stellt sie in die Warteschlange. Am anderen Ende der Warteschlange befindet sich ein Thread namens Ereigniskonsument, der kontinuierlich abfragt, ob sich Ereignisse in der Warteschlange befinden Wenn es Ereignisse gibt, wird es sofort ausgeführt, um zu verhindern, dass sich blockierende Vorgänge während der Ausführung auf die Lesewarteschlange des aktuellen Threads auswirken.

Der Mechanismus des Javascript-Frontends und von Node.js ähnelt diesem Ereignisabfragemodell. Einige Leute denken, dass Node.js Single-Threaded ist Der Ereigniskonsument ist Single-Threaded. Was soll ich tun, wenn ein blockierender Vorgang vorliegt? Blockiert er nicht die aktuelle Single-Threaded-Ausführung?

Tatsächlich verfügt Node.js auch über einen Thread-Pool an der Unterseite. Der Thread-Pool wird speziell zum Ausführen verschiedener Blockierungsvorgänge verwendet. Dies hat keinen Einfluss auf die Warteschlangen-Ereignisabfrage und -Abfrage Nachdem der Thread-Pool die Operation abgeschlossen hat, fungiert er als Ereignisproduzent und stellt die Operationsergebnisse in dieselbe Warteschlange.

Kurz gesagt, eine Ereignisabfrage-Ereignisschleife erfordert drei Komponenten:

Ereigniswarteschlange, die zum FIFO-Modell gehört. Ein Ende überträgt Ereignisdaten und das andere Ende ruft Ereignisdaten ab Die beiden Enden kommunizieren nur über diese Warteschlange, bei der es sich um eine asynchrone lose Kopplung handelt. Der Leseabfragethread der Warteschlange, der Verbraucher des Ereignisses und der Protagonist der Ereignisschleife. Ein separater Thread-Pool, Thread Pool, wird speziell für die Ausführung langer Aufgaben, schwerer Aufgaben und schwerer körperlicher Arbeit verwendet.

Node.js ist ebenfalls eine Single-Threaded-Ereignisschleife, ihr Betriebsmechanismus unterscheidet sich jedoch von der Browserumgebung.

Gemäß der obigen Abbildung ist der Betriebsmechanismus von Node.js wie folgt.

V8-Engine analysiert JavaScript-Skripte. Der analysierte Code ruft die Node-API auf. Die libuv-Bibliothek ist für die Ausführung der Node-API verantwortlich. Es weist verschiedenen Threads unterschiedliche Aufgaben zu, um eine Ereignisschleife (Ereignisschleife) zu bilden, und gibt die Ausführungsergebnisse der Aufgaben asynchron an die V8-Engine zurück. Die V8-Engine gibt die Ergebnisse dann an den Benutzer zurück.

Wir können sehen, dass der Kern von node.js tatsächlich die libuv-Bibliothek ist. Diese Bibliothek ist in C geschrieben und kann Multithreading-Technologie nutzen, während unsere Javascript-Anwendung Single-Threaded ist.

Der asynchrone Aufgabenausführungsprozess von Nodejs:

Der vom Benutzer geschriebene Code ist Single-Threaded, aber NodeJS ist intern nicht Single-Threaded!

Ereignismechanismus:

Node.js verwendet nicht mehrere Threads, um die Arbeit für jede Anfrage auszuführen. Stattdessen fügt es die gesamte Arbeit einer Ereigniswarteschlange hinzu und trennt sie dann dort Thread zum Durchlaufen der Ereignisse in der Warteschlange. Der Ereignisschleifen-Thread greift auf den obersten Eintrag in der Ereigniswarteschlange zu, führt ihn aus und greift dann auf den nächsten Eintrag zu. Bei der Ausführung von lang laufendem oder blockierendem E/A-Code

In Node.js, da es nur einen einzigen Thread gibt, der ständig die Warteschlange nach Ereignissen abfragt, für E/A-Vorgänge wie Datenbankdateisysteme, einschließlich HTTP-Anfragen und andere Vorgänge, die leicht zu blockieren und abzuwarten sind. Wenn sie auch in diesem einzelnen Thread implementiert werden, blockieren sie definitiv die Ausführung anderer Aufgaben. Javascript/Node.js delegiert sie zur Ausführung an den zugrunde liegenden Thread-Pool Teilen Sie dem Thread-Pool eine Rückruffunktion mit, damit der einzelne Thread weiterhin andere Dinge ausführt. Wenn diese Blockierungsvorgänge abgeschlossen sind, werden die Ergebnisse zusammen mit der bereitgestellten Rückruffunktion in die Warteschlange gestellt In der Warteschlange werden diese blockierten Vorgänge gelesen. Nach der Verarbeitung der Ergebnisse werden diese Vorgangsergebnisse als Eingabeparameter der Rückruffunktion verwendet und anschließend die Rückruffunktion zur Ausführung aktiviert.

Bitte beachten Sie, dass der einzelne Thread von Node.js nicht nur für das Lesen von Warteschlangenereignissen verantwortlich ist, sondern auch für die Ausführung der Rückruffunktion. Dies ist eine wichtige Funktion, die ihn vom Multithread-Modus unterscheidet Modus, ein einzelner Thread Der Thread ist nur für das Lesen von Warteschlangenereignissen verantwortlich und erledigt keine anderen Aufgaben mehr. Er wird andere Threads mit anderen Aufgaben beauftragen, insbesondere im Fall von Mehrkernen ist ein CPU-Kern für das Lesen von Warteschlangenereignissen verantwortlich , und ein CPU-Kern ist für die Ausführung aktivierter Aufgaben verantwortlich. Auf diese Weise am besten für CPU-intensive Aufgaben geeignet. Die Ausführungsaktivierungsaufgabe von Node..js, also die Aufgabe in der Rückruffunktion, wird wiederum weiterhin in dem einzelnen Thread ausgeführt, der für die Abfrage verantwortlich ist, was verhindern soll, dass CPU-intensive Aufgaben wie Konvertierungen ausgeführt werden Wenn Sie JSON in andere Datenformate usw. umwandeln, wirken sich diese Aufgaben auf die Effizienz der Ereignisabfrage aus.

5. Nodejs-Funktionen

Die hervorstechenden Merkmale von NodeJS: asynchroner Mechanismus, ereignisgesteuert.

Der gesamte Prozess der Ereignisabfrage blockiert nicht die Verbindung neuer Benutzer und es besteht keine Notwendigkeit, die Verbindung aufrechtzuerhalten. Basierend auf diesem Mechanismus kann NodeJS theoretisch auf Benutzer reagieren, die nacheinander Verbindungen anfordern. Daher kann NodeJS eine höhere Parallelität unterstützen als Java- und PHP-Programme.

Obwohl die Verwaltung der Ereigniswarteschlange auch Kosten verursacht und NodeJS Single-Threading ist, dauert es umso länger, bis eine Antwort eingeht, je länger die Ereigniswarteschlange ist, und die Parallelität ist immer noch unzureichend.

RESTful API是NodeJS最理想的应用场景,可以处理数万条连接,本身没有太多的逻辑,只需要请求API,组织数据进行返回即可。

6. 实例

看一个具体实例:

console.log('1')
setTimeout(function() {
 console.log('2')
 new Promise(function(resolve) {
 console.log('4')
 resolve()
 }).then(function() {
 console.log('5')
 })
 setTimeout(() => {
 console.log('haha')
 })
 new Promise(function(resolve) {
 console.log('6')
 resolve()
 }).then(function() {
 console.log('66')
 })
})
setTimeout(function() {
 console.log('hehe')
}, 0)
new Promise(function(resolve) {
 console.log('7')
 resolve()
}).then(function() {
 console.log('8')
})
setTimeout(function() {
 console.log('9')
 new Promise(function(resolve) {
 console.log('11')
 resolve()
 }).then(function() {
 console.log('12')
 })
})
new Promise(function(resolve) {
 console.log('13')
 resolve()
}).then(function() {
 console.log('14')
})
// node1 : 1,7,13,8,14,2,4,6,hehe,9,11,5,66,12,haha // 结果不稳定
// node2 : 1,7,13,8,14,2,4,6,hehe,5,66,9,11,12,haha // 结果不稳定
// node3 : 1,7,13,8,14,2,4,6,5,66,hehe,9,11,12,haha // 结果不稳定
// chrome : 1,7,13,8,14,2,4,6,5,66,hehe,9,11,12,haha


chrome的运行比较稳定,而node环境下运行不稳定,可能会出现两种情况。

chrome运行的结果的原因是Promiseprocess.nextTick()的微任务Event Queue运行的权限比普通宏任务Event Queue权限高,如果取事件队列中的事件的时候有微任务,就先执行微任务队列里的任务,除非该任务在下一轮的Event Loop中,微任务队列清空了之后再执行宏任务队列里的任务。

相关推荐:

Node.js事件循环教程

javascript事件循环之强制梳理

深入理解Node.js 事件循环和回调函数

Das obige ist der detaillierte Inhalt vonDetaillierte Erklärung der Ereignisschleife in JS und Node.js. Für weitere Informationen folgen Sie bitte anderen verwandten Artikeln auf der PHP chinesischen Website!

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