Heim > Web-Frontend > js-Tutorial > Analyse des Ereignisschleifenmechanismus von Node.js

Analyse des Ereignisschleifenmechanismus von Node.js

不言
Freigeben: 2018-10-20 16:25:29
nach vorne
2824 Leute haben es durchsucht

Der Inhalt dieses Artikels befasst sich mit der Analyse des Ereignisschleifenmechanismus von Node.js. Ich hoffe, dass er für Freunde hilfreich ist.

Im Browser-Artikel wurden der Ereignisschleifenmechanismus und einige verwandte Konzepte ausführlich vorgestellt, dies dient jedoch hauptsächlich der Forschung auf der Browserseite. Gilt das auch für die Node-Umgebung? Schauen wir uns zunächst eine Demo an:

setTimeout(()=>{
    console.log('timer1')
    Promise.resolve().then(function() {
        console.log('promise1')
    })}, 0)setTimeout(()=>{
    console.log('timer2')
    Promise.resolve().then(function() {
        console.log('promise2')
    })}, 0)
Nach dem Login kopieren

Kompilieren Sie es mit bloßem Auge und führen Sie es im Browser aus. Sie kennen die Wahrheit bereits, daher werde ich nicht auf Details eingehen.

timer1
promise1
timer2
promise2
Nach dem Login kopieren

Dann führe es unter Node aus, eh. . . Seltsam, das laufende Ergebnis unterscheidet sich vom Browser~

timer1
timer2
promise1
promise2
Nach dem Login kopieren

Das Beispiel zeigt, dass der Ereignisschleifenmechanismus des Browsers und von Node.js unterschiedlich sind, werfen wir einen Blick darauf~

Ereignisverarbeitung von Node.js

Node.js verwendet V8 als Parsing-Engine von js und verwendet eine eigene libuv für die E/A-Verarbeitung. libuv ist eine ereignisgesteuerte plattformübergreifende Abstraktionsschicht, die einige zugrunde liegende Funktionen von kapselt Verschiedene Betriebssysteme stellen eine einheitliche API für die Außenwelt bereit. Der Ereignisschleifenmechanismus ist darin ebenfalls implementiert:

int uv_run(uv_loop_t* loop, uv_run_mode mode) {
  int timeout;
  int r;
  int ran_pending;
  r = uv__loop_alive(loop);
  if (!r)
    uv__update_time(loop);
  while (r != 0 && loop->stop_flag == 0) {
    uv__update_time(loop);
    // timers阶段
    uv__run_timers(loop);
    // I/O callbacks阶段
    ran_pending = uv__run_pending(loop);
    // idle阶段
    uv__run_idle(loop);
    // prepare阶段
    uv__run_prepare(loop);
    timeout = 0;
    if ((mode == UV_RUN_ONCE && !ran_pending) || mode == UV_RUN_DEFAULT)
      timeout = uv_backend_timeout(loop);
    // poll阶段
    uv__io_poll(loop, timeout);
    // check阶段
    uv__run_check(loop);
    // close callbacks阶段
    uv__run_closing_handles(loop);
    if (mode == UV_RUN_ONCE) {
      uv__update_time(loop);
      uv__run_timers(loop);
    }
    r = uv__loop_alive(loop);
    if (mode == UV_RUN_ONCE || mode == UV_RUN_NOWAIT)
      break;
  }
  if (loop->stop_flag != 0)
    loop->stop_flag = 0;
  return r;
}
Nach dem Login kopieren

Laut der offiziellen Einführung von Node.js enthält jede Ereignisschleife 6 Stufen . , entsprechend der Implementierung im libuv-Quellcode, wie in der Abbildung unten gezeigt

Analyse des Ereignisschleifenmechanismus von Node.js

Timer-Stufe: Diese Stufe führt den Rückruf von Timer (setTimeout, setInterval) aus

E/A-Rückrufphase: Führen Sie einige Systemaufruffehler aus, z. B. Netzwerkkommunikationsfehler-Rückrufe

Leerlauf, Vorbereitungsphase: Wird nur intern vom Knoten verwendet

Abfragephase: Neues I abrufen /O-Ereignisse, entsprechend Unter den Bedingungen wird der Knoten hier blockiert

Prüfphase: Führen Sie den Rückruf von setImmediate() aus

Rückrufphase schließen: Führen Sie den Rückruf des Schließereignisses des Sockets aus

Konzentrieren wir uns auf Timer und Umfragen. Überprüfen Sie einfach diese drei Phasen, da die meisten asynchronen Aufgaben in der täglichen Entwicklung in diesen drei Phasen verarbeitet werden.

Timer-Phase

Timer ist die erste Phase der Ereignisschleife, Node Überprüft, ob ein Timer abgelaufen ist, und wenn ja, wird der Rückruf in die Taskwarteschlange des Timers verschoben, um auf die Ausführung zu warten Es gibt keine Garantie dafür, dass der Timer sofort ausgeführt wird, wenn die voreingestellte Zeit erreicht ist, da die Ablaufprüfung des Timers durch den Knoten nicht unbedingt zuverlässig ist. Dies wird von anderen laufenden Programmen auf dem Computer beeinflusst oder der Hauptthread ist nicht inaktiv damals. Im folgenden Code ist beispielsweise die Ausführungsreihenfolge von setTimeout() und setImmediate() ungewiss.

setTimeout(() => {
  console.log('timeout')
  }, 0)
  setImmediate(() => {
  console.log('immediate')
  })
Nach dem Login kopieren

Wenn Sie sie jedoch in einen I/O-Rückruf einfügen, muss setImmediate() zuerst ausgeführt werden, da auf die Abfragephase die Prüfphase folgt.

Umfragephase

Die Umfragephase hat hauptsächlich zwei Funktionen:

Verarbeitung von Ereignissen in der Umfragewarteschlange

Wenn ein Timer abgelaufen ist, Führen Sie seine Rückruffunktion aus.

Die gerade Schleife führt die Rückrufe in der Abfragewarteschlange synchron aus, bis die Warteschlange leer ist oder die ausgeführten Rückrufe die Obergrenze des Systems erreichen (die Obergrenze ist unbekannt). Anschließend prüft die gerade Schleife, ob Es gibt ein voreingestelltes setImmediate(), das in zwei Situationen unterteilt ist:

Wenn es ein voreingestelltes setImmediate() gibt, beendet die Ereignisschleife die Abfragephase, tritt in die Prüfphase ein und führt die Aufgabenwarteschlange der Prüfung aus Phase

Wenn kein voreingestelltes setImmediate() vorhanden ist, blockiert die Ereignisschleife und wartet in dieser Phase

Beachten Sie ein Detail: Kein setImmediate() löst ein Ereignis aus Die Schleife ist in der Poll-Phase blockiert, könnte also der zuvor eingestellte Timer nicht nicht ausgeführt werden? Also, in der Umfragephase Die Schleife verfügt über einen Prüfmechanismus, um zu prüfen, ob die Timer-Warteschlange leer ist. Wenn die Timer-Warteschlange nicht leer ist, wird das Ereignis ausgeführt Die Schleife startet die nächste Runde der Ereignisschleife, d. h. sie tritt erneut in die Timer-Phase ein.

Prüfphase

Der Rückruf von setImmediate() wird zur Prüfwarteschlange hinzugefügt. Aus dem Phasendiagramm der Ereignisschleife können wir erkennen, dass die Ausführungsreihenfolge der Prüfphase nach der Abfrage liegt Phase.

Zusammenfassung

Jede Stufe der Ereignisschleife hat eine Aufgabenwarteschlange

Wenn die Ereignisschleife eine bestimmte Stufe erreicht, wird die Aufgabenwarteschlange dieser Stufe bis zur Warteschlange ausgeführt wird gelöscht. Oder wenn der ausgeführte Rückruf das Systemlimit erreicht, geht er zur nächsten Stufe über

Wenn alle Stufen einmal nacheinander ausgeführt werden, gilt die Ereignisschleife als abgeschlossenes Tick

Das macht Sinn. Aber ohne die Demo verstehe ich es immer noch nicht ganz.

const fs = require('fs')fs.readFile('test.txt', () => {
  console.log('readFile')
  setTimeout(() => {
    console.log('timeout')
  }, 0)
  setImmediate(() => {
    console.log('immediate')
  })
  })
Nach dem Login kopieren

Es sollte kein Zweifel an den Ausführungsergebnissen bestehen

readFile
immediate
timeout
Nach dem Login kopieren

Unterschiede zwischen Node.js und der Browser-Ereignisschleife

Überprüfung des vorherigen Artikels, Mikrotask-Aufgaben in der Browserumgebung Die Warteschlange wird nach der Ausführung jeder Makroaufgabe ausgeführt.

Analyse des Ereignisschleifenmechanismus von Node.js

In Node.js werden Mikrotasks zwischen verschiedenen Phasen der Ereignisschleife ausgeführt, d. h. nach Ausführung einer Phase werden die Aufgaben in der Mikrotask-Warteschlange ausgeführt .

Analyse des Ereignisschleifenmechanismus von Node.js

Demo-Rezension

Bei der Durchsicht der Demo am Anfang des Artikels wird das globale Skript (main()) ausgeführt. und die beiden Timer werden nacheinander in die Timer-Warteschlange gestellt, main() wird ausgeführt, der Aufrufstapel ist inaktiv und die Task-Warteschlange beginnt mit der Ausführung

Analyse des Ereignisschleifenmechanismus von Node.js

首先进入timers阶段,执行timer1的回调函数,打印timer1,并将promise1.then回调放入microtask队列,同样的步骤执行timer2,打印timer2;

至此,timer阶段执行结束,event loop进入下一个阶段之前,执行microtask队列的所有任务,依次打印promise1、promise2。

对比浏览器端的处理过程:

Analyse des Ereignisschleifenmechanismus von Node.js

process.nextTick() VS setImmediate()

In essence, the names should be swapped. process.nextTick() fires more immediately than setImmediate()

来自官方文档有意思的一句话,从语义角度看,setImmediate() 应该比 process.nextTick() 先执行才对,而事实相反,命名是历史原因也很难再变。

process.nextTick() 会在各个事件阶段之间执行,一旦执行,要直到nextTick队列被清空,才会进入到下一个事件阶段,所以如果递归调用 process.nextTick(),会导致出现I/O starving(饥饿)的问题,比如下面例子的readFile已经完成,但它的回调一直无法执行:

const fs = require('fs')const starttime = Date.now()let endtime
fs.readFile('text.txt', () => {
  endtime = Date.now()
  console.log('finish reading time: ', endtime - starttime)})let index = 0function handler () {
  if (index++ >= 1000) return
  console.log(`nextTick ${index}`)
  process.nextTick(handler)
  // console.log(`setImmediate ${index}`)
  // setImmediate(handler)}handler()
Nach dem Login kopieren

process.nextTick()的运行结果:

nextTick 1
nextTick 2
......
nextTick 999
nextTick 1000
finish reading time: 170
Nach dem Login kopieren

替换成setImmediate(),运行结果:

setImmediate 1
setImmediate 2
finish reading time: 80
......
setImmediate 999
setImmediate 1000
Nach dem Login kopieren

这是因为嵌套调用的 setImmediate() 回调,被排到了下一次event loop才执行,所以不会出现阻塞。

总结

1、Node.js 的事件循环分为6个阶段

2、浏览器和Node 环境下,microtask 任务队列的执行时机不同

Node.js中,microtask 在事件循环的各个阶段之间执行

浏览器端,microtask 在事件循环的 macrotask 执行完之后执行

3、递归的调用process.nextTick()会导致I/O starving,官方推荐使用setImmediate()

Das obige ist der detaillierte Inhalt vonAnalyse des Ereignisschleifenmechanismus von Node.js. Für weitere Informationen folgen Sie bitte anderen verwandten Artikeln auf der PHP chinesischen Website!

Verwandte Etiketten:
Quelle:github.io
Erklärung dieser Website
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
Beliebte Tutorials
Mehr>
Neueste Downloads
Mehr>
Web-Effekte
Quellcode der Website
Website-Materialien
Frontend-Vorlage