Heim  >  Artikel  >  Web-Frontend  >  Lassen Sie uns über Prozesse, Threads, Coroutinen und Parallelitätsmodelle in Node.js sprechen

Lassen Sie uns über Prozesse, Threads, Coroutinen und Parallelitätsmodelle in Node.js sprechen

青灯夜游
青灯夜游nach vorne
2022-07-19 20:11:072512Durchsuche

Lassen Sie uns über Prozesse, Threads, Coroutinen und Parallelitätsmodelle in Node.js sprechen

Node.js ist jetzt Mitglied der Toolbox für den Aufbau von Netzwerkanwendungsdiensten mit hoher Parallelität. Warum ist Node.js zum Publikumsliebling geworden? Dieser Artikel beginnt mit den Grundkonzepten von Prozessen, Threads, Coroutinen und E/A-Modellen und gibt Ihnen eine umfassende Einführung in Node.js und das Parallelitätsmodell.

Prozess

Wir nennen die laufende Instanz eines Programms im Allgemeinen einen Prozess. Es handelt sich um eine Grundeinheit für die Ressourcenzuweisung und -planung durch das Betriebssystem. Es umfasst im Allgemeinen die folgenden Teile:

  • Programm: Das ist Der auszuführende Code wird verwendet, um die vom Prozess auszuführende Funktion zu beschreiben.
  • Datenbereich: der vom Prozess verarbeitete Datenraum, einschließlich Daten, dynamisch zugewiesenem Speicher, Benutzerstapel von Verarbeitungsfunktionen, änderbaren Programmen und anderen Informationen
  • Prozesstabellenelement: Um das Prozessmodell zu implementieren, verwaltet das Betriebssystem eine Tabelle namens 进程表的表格,每个进程占用一个进程表项(也叫进程控制块, die wichtige Prozessstatusinformationen wie Programmzähler, Stapelzeiger, Speicherzuordnung, Status geöffneter Dateien, Planungsinformationen usw. enthält. Dadurch wird sichergestellt, dass das Betriebssystem den Prozess ordnungsgemäß wiederbeleben kann, nachdem ein Prozess angehalten wurde.

Prozesse haben die folgenden Eigenschaften:

  • Dynamik: Die Essenz eines Prozesses ist ein Ausführungsprozess eines Programms in einem Multiprogrammiersystem. Prozesse werden dynamisch generiert und sterben dynamisch.
  • Parallelität: Jeder Prozess kann mit interagieren andere Prozesse Prozesse werden gleichzeitig ausgeführt;
  • Unabhängigkeit: Ein Prozess ist eine Grundeinheit, die unabhängig ausgeführt werden kann, und es ist auch eine unabhängige Einheit für die Systemzuordnung und -planung.
  • Asynchronität: Aufgrund gegenseitiger Einschränkungen zwischen Prozessen werden Prozesse intermittierend ausgeführt , das heißt, Prozesse schreiten mit unabhängiger und unvorhersehbarer Geschwindigkeit voran.

Es ist zu beachten, dass, wenn ein Programm zweimal ausgeführt wird, selbst wenn das Betriebssystem die gemeinsame Nutzung von Code ermöglichen kann (d. h. nur eine Kopie des Codes befindet sich im Speicher), dies die beiden Instanzen des ausgeführten Programms nicht ändern kann Es handelt sich um zwei Tatsachen unterschiedlicher Prozesse.

Während der Ausführung des Prozesses wechselt der Prozess aus verschiedenen Gründen wie Unterbrechungen und CPU-Planung zwischen den folgenden Zuständen:

Lassen Sie uns über Prozesse, Threads, Coroutinen und Parallelitätsmodelle in Node.js sprechen

  • Laufstatus: Der Prozess läuft in diesem Moment und belegt die CPU;
  • Bereit-Zustand: Der Prozess ist in diesem Moment bereit und kann jederzeit ausgeführt werden, wird jedoch vorübergehend angehalten, da andere Prozesse ausgeführt werden.
  • Blockierter-Zustand: Der Prozess befindet sich in diesem Moment in einem blockierten Zustand, sofern kein externer Ereignis (z. B. Tastatureingabedaten sind eingetroffen) eintritt, andernfalls wird der Prozess nicht ausgeführt.

Wie aus dem Prozessstatus-Umschaltdiagramm oben ersichtlich ist, kann der Prozess vom laufenden Zustand in den Bereitschaftszustand und den blockierten Zustand wechseln, aber nur der Bereitschaftszustand kann direkt in den laufenden Zustand umgeschaltet werden. Dies liegt daran, dass:

  • wechselt vom laufenden Zustand in den Bereitschaftszustand. Dies wird durch den Prozessplaner verursacht, da das System davon ausgeht, dass der aktuelle Prozess zu viel CPU-Zeit beansprucht hat, und beschließt, die CPU-Zeit und den Prozessplaner anderen Prozessen zu überlassen ist Teil des Betriebssystems und der Prozess spürt nicht einmal die Existenz des Planers.
  • Der Wechsel vom laufenden Zustand in den blockierenden Zustand erfolgt aus eigenen Gründen (z. B. Warten auf die Tastatureingabe des Benutzers). Der Prozess kann nicht weiter ausgeführt werden und kann nur warten, bis ein bestimmtes Ereignis (z. B. die Dateneingabe über die Tastatur) eintritt. Wenn dies eintritt, wechselt der Prozess zunächst in den Bereitschaftszustand Wenn zu diesem Zeitpunkt ein anderer Prozess ausgeführt wird, wechselt er sofort in den Ausführungsstatus. Andernfalls bleibt der Prozess im Bereitschaftsstatus und wartet auf die Planung durch den Prozessplaner.

Threads

Manchmal müssen wir Threads verwenden, um die folgenden Probleme zu lösen:

  • Mit zunehmender Anzahl von Prozessen werden die Kosten für den Wechsel zwischen Prozessen und die effektive Nutzung der CPU immer größer wird auch immer niedriger, was in schweren Fällen dazu führen kann, dass das System einfriert Bei einigen Daten ist die Datensynchronisierung zwischen mehreren Prozessen zu umständlich.
  • Über Threads müssen wir die folgenden Punkte wissen:

Ein Thread ist ein einzelner sequenzieller Kontrollfluss in der Programmausführung. Er ist die kleinste Einheit, die das Betriebssystem ausführen kann. Er ist im Prozess enthalten ist der Kern des Prozesses.
  • Ein Prozess kann mehrere Threads enthalten. Jeder Thread führt unterschiedliche Aufgaben parallel aus.
  • Alle Threads in einem Prozess teilen sich den Speicherraum des Prozesses (einschließlich Code, Daten, Heap, usw.) und einige Ressourceninformationen (z. B. offene Dateien und Systemsignale);
  • Threads in einem Prozess sind in anderen Prozessen nicht sichtbar.
  • Da wir nun die grundlegenden Eigenschaften von Gewinden verstanden haben, sprechen wir über einige gängige Gewindetypen.

Kernel-Status-Threads

Kernel-Status-Threads sind Threads, die direkt vom Betriebssystem unterstützt werden:

  • Die Erstellung, Planung, Synchronisierung und Zerstörung von Threads wird vom Systemkern durchgeführt, der Overhead ist jedoch relativ kostspielig.
  • Der Kernel kann jedem Prozessor Kernel-Status-Threads zuordnen, sodass ein Prozessorkern problemlos korrespondieren kann zu einem Kernel-Thread. Dadurch konkurrieren sie vollständig um CPU-Ressourcen und nutzen nur den Kernel-Code und die Daten. Die Effizienz der Ressourcensynchronisierung und Datenfreigabe ist geringer als die des Prozesses.
  • Benutzermodus-Thread

Benutzermodus-Thread ist ein vollständig im Benutzerbereich integrierter Thread. Seine Hauptfunktionen sind wie folgt:

Die Erstellung, Planung, Synchronisierung und Zerstörung von Threads erfolgt durch den Benutzerbereich. und sein Overhead ist sehr gering;

Da Benutzermodus-Threads vom Benutzerbereich verwaltet werden, erkennt der Kernel die Existenz von Benutzermodus-Threads überhaupt nicht. Daher plant und weist der Kernel nur den Prozessen Ressourcen zu, denen sie zugewiesen sind gehören, und die Planung und Ressourcenzuweisung der Threads im Prozess wird vom Programm selbst übernommen. Wenn ein Benutzermodus-Thread in einem Systemaufruf blockiert wird, ist es sehr wahrscheinlich, dass der gesamte Prozess blockiert wird um auf alle gemeinsam genutzten Adressräume und Systemressourcen des Prozesses zuzugreifen, zu dem es gehört;
  • Ressourcensynchronisierung und Datenfreigabe sind effizienter.
  • Lightweight Process (LWP)
  • Lightweight Process (LWP) ist ein Benutzer-Thread, der auf dem Kernel aufbaut und von diesem unterstützt wird:

Der Benutzerbereich kann nur über Lightweight Lightweight ausgeführt werden Prozesse (LWP) verwenden Kernel-Threads, die als Brücke zwischen Benutzermodus-Threads und Kernel-Threads betrachtet werden können. Daher können wir leichtgewichtige Prozesse (LWP) haben Der Level-Prozess (LWP) erfordert den Benutzermodusraum, um einen Systemaufruf zu initiieren. Die Kosten für diesen Systemaufruf sind relativ hoch (erfordert das Umschalten zwischen Benutzermodus und Kernelmodus). Jeder Lightweight-Prozess (LWP) muss zugeordnet werden mit einem bestimmten Kernel-Thread, daher:

Wie Kernel-Threads können sie systemweit vollständig konkurrieren und CPU-Ressourcen nutzen
  • Jeder Lightweight-Prozess (LWP) ist eine unabhängige Thread-Planungseinheit, sodass selbst wenn ein Lightweight-Prozess (; LWP) in einem Systemaufruf blockiert wird, hat dies keinen Einfluss auf die Ausführung des gesamten Prozesses Lightweight-Prozesse (LWP) im System;

  • können auf alle gemeinsam genutzten Adressräume und Systemressourcen des Prozesses zugreifen, zu dem sie gehören.

  • Zusammenfassung
    • Oben haben wir kurz gängige Thread-Typen vorgestellt (Kernel-Status-Threads, Benutzer-Status-Threads, Lightweight-Prozesse). Jeder von ihnen hat seinen eigenen Anwendungsbereich und kann im tatsächlichen Gebrauch verwendet werden Kombinieren Sie sie frei und verwenden Sie sie entsprechend Ihren eigenen Anforderungen, z. B. gängige Eins-zu-Eins-, Viele-zu-Eins-, Viele-zu-Viele- und andere Modelle. Aus Platzgründen wird in diesem Artikel nicht zu viel darüber vorgestellt. Interessierte Studierende können es selbst studieren.
    • Coroutine
  • Coroutine, auch Fiber genannt, ist ein Programmausführungsmechanismus, der auf Threads basiert und von Entwicklern verwaltet wird, um Planung, Zustandswartung und andere Verhaltensweisen durchzuführen:
  • Weil keine Ausführungsplanung erforderlich ist Kontextwechsel, gute Ausführungseffizienz;
Da es auf demselben Thread ausgeführt wird, gibt es kein Synchronisationsproblem bei der Thread-Kommunikation.

Bequemer Wechsel des Kontrollflusses und vereinfachtes Programmiermodell.

In JavaScript ist der async/await, den wir häufig verwenden, eine Implementierung von Coroutine, wie im folgenden Beispiel:

function updateUserName(id, name) {
  const user = getUserById(id);
  user.updateName(name);
  return true;
}

async function updateUserNameAsync(id, name) {
  const user = await getUserById(id);
  await user.updateName(name);
  return true;
}

Im obigen Beispiel ist die Funktion updateUserName Die logische Die Ausführungssequenz innerhalb von Code> und <code>updateUserNameAsync ist:

Rufen Sie die Funktion getUserById auf und weisen Sie ihren Rückgabewert der Variablen user zu.

Aufruf die updateName-Methode von user
  • gibt true an den Aufrufer zurück.
  • Der Hauptunterschied zwischen den beiden ist die Zustandskontrolle während des eigentlich laufenden Prozesses:
Während der Ausführung der Funktion updateUserName wird diese der Reihe nach gemäß der oben genannten logischen Reihenfolge ausgeführt;

async/await 便是协程的一种实现,比如下面的例子:

rrreee

上例中,函数 updateUserNameupdateUserNameAsync 内的逻辑执行顺序是:

  • 调用函数 getUserById 并将其返回值赋给变量 user
  • 调用 userupdateName 方法;
  • 返回 true 给调用者。

两者的主要区别在于其实际运行过程中的状态控制:

  • 在函数 updateUserName 的执行过程中,按照前文所述的逻辑顺序依次执行;
  • 在函数 updateUserNameAsync 的执行过程中,同样按照前文所述的逻辑顺序依次执行,只不过在遇到 await 时,updateUserNameAsync 将会被挂起并保存挂起位置当前的程序状态,直到 await 后面的程序片段返回后,才会再次唤醒 updateUserNameAsync 并恢复挂起前的程序状态,然后继续执行下一段程序。

通过上面的分析我们可以大胆猜测:协程要解决的并非是进程、线程要解决的程序并发问题,而是要解决处理异步任务时所遇到的问题(比如文件操作、网络请求等);在 async/await 之前,我们只能通过回调函数来处理异步任务,这很容易使我们陷入回调地狱In der Funktion wird während der Ausführung von updateUserNameAsync auch in der gleichen logischen Reihenfolge wie oben erwähnt ausgeführt, mit der Ausnahme, dass updateUserNameAsyncawait angetroffen wird /code> wird angehalten und speichert den aktuellen Programmstatus an der angehaltenen Position, bis das Programmfragment nach await zurückkehrt, wird updateUserNameAsync wieder aktiviert und der Programmstatus vor dem Anhalten wird wiederhergestellt, und fahren Sie dann mit dem nächsten Programm fort.

🎜🎜Durch die obige Analyse können wir mutig raten: Was Coroutinen lösen müssen, sind nicht die Probleme der Programmparallelität, die Prozesse und Threads lösen müssen, sondern die Probleme, die bei der Verarbeitung asynchroner Aufgaben (z. B. Dateivorgänge, Netzwerkanforderungen usw.) auftreten ). Eine Menge Scheiße. Durch Coroutinen können wir den Zweck der Synchronisierung von asynchronem Code erreichen. 🎜

Was man bedenken muss, ist, dass die Kernfunktion von Coroutinen darin besteht, ein bestimmtes Programm anzuhalten und den Zustand der angehaltenen Position des Programms beizubehalten und irgendwann in der Zukunft an der angehaltenen Position fortzufahren Fahren Sie mit der Ausführung des nächsten Schritts nach der angehaltenen Position A fort.

I/O-Modell

Eine vollständige I/O-Operation muss die folgenden Phasen durchlaufen: I/O 操作需要经历以下阶段:

  • 用户进(线)程通过系统调用向内核发起 I/O 操作请求;
  • 内核对 I/O 操作请求进行处理(分为准备阶段和实际执行阶段),并将处理结果返回给用户进(线)程。

我们可将 I/O 操作大致分为阻塞 I/O非阻塞 I/O同步 I/O异步 I/O 四种类型,在讨论这些类型之前,我们先熟悉下以下两组概念(此处假设服务 A 调用了服务 B):

  • 阻塞/非阻塞

    • 如果 A 只有在接收到 B 的响应之后才返回,那么该调用为阻塞调用
    • 如果 A 调用 B 后立即返回(即无需等待 B 执行完毕),那么该调用为非阻塞调用
  • 同步/异步

    • 如果 B 只有在执行完之后再通知 A,那么服务 B 是同步的;
    • 如果 A 调用 B 后,B 立刻给 A 一个请求已接收的通知,然后在执行完之后通过回调的方式将执行结果通知给 A,那么服务 B 就是异步的。

很多人经常将阻塞/非阻塞同步/异步搞混淆,故需要特别注意:

  • 阻塞/非阻塞针对于服务的调用者而言;
  • 同步/异步针对于服务的被调用者而言。

了解了阻塞/非阻塞同步/异步,我们来看具体的 I/O 模型

阻塞 I/O

定义:用户进(线)程发起 I/O 系统调用后,用户进(线)程会被立即阻塞,直到整个 I/O 操作处理完毕并将结果返回给用户进(线)程后,用户进(线)程才能解除阻塞状态,继续执行后续操作。

特点:

  • 由于该模型会阻塞用户进(线)程,因此该模型不占用 CPU 资源;
  • 在执行 I/O 操作的时候,用户进(线)程不能进行其它操作;
  • 该模型仅适用于并发量小的应用,这是因为一个 I/O 请求就能阻塞进(线)程,所以为了能够及时响应 I/O 请求,需要为每个请求分配一个进(线)程,这样会造成巨大的资源占用,并且对于长连接请求来说,由于进(线)程资源长期得不到释放,如果后续有新的请求,将会产生严重的性能瓶颈。

非阻塞 I/O

定义:

  • 用户进(线)程发起 I/O 系统调用后,如果该 I/O 操作未准备就绪,该 I/O 调用将会返回一个错误,用户进(线)程也无需等待,而是通过轮询的方式来检测该 I/O 操作是否就绪;
  • 操作就绪后,实际的 I/O 操作会阻塞用户进(线)程直到执行结果返回给用户进(线)程。

特点:

  • 由于该模型需要用户进(线)程不断地询问 I/O 操作就绪状态(一般使用 while 循环),因此该模型需占用 CPU,消耗 CPU 资源;
  • I/O 操作就绪前,用户进(线)程不会阻塞,等到 I/O 操作就绪后,后续实际的 I/O 操作将阻塞用户进(线)程;
  • 该模型仅适用于并发量小,且不需要及时响应的应用。

同(异)步 I/O

用户进(线)程发起 I/O 系统调用后,如果该 I/O 调用会导致用户进(线)程阻塞,那么该 I/O 调用便为同步 I/O,否则为 异步 I/O

判断 I/O 操作同步异步的标准是用户进(线)程与 I/O

  • Der Benutzerprozess (Thread) initiiert einen Systemaufruf an den Kernel-I/O-Operationsanforderung;
  • Der Kernel verarbeitet die I/O-Operationsanforderung (unterteilt in Vorbereitungsphase und tatsächliche Ausführungsphase) und gibt sie zurück Das Verarbeitungsergebnis gibt Benutzern einen (Thread-)Prozess.
Wir können I/O-Operationen grob in blockierende I/O und nicht-blockierende I/O unterteilen , Synchronous I/O, Asynchronous I/O vier Typen Bevor wir diese Typen diskutieren, machen wir uns zunächst mit den folgenden zwei Konzepten vertraut (hier wird davon ausgegangen, dass Dienst A ruft Dienst B an): 🎜
  • 🎜Blockierend/Nicht blockierend: 🎜
    • Wenn A erst nach Erhalt der Antwort von B zurückkehrt, lautet der Anruf Blockierender Aufruf;
    • Wenn A unmittelbar nach dem Aufruf von B zurückkehrt (d. h. ohne darauf zu warten, dass B die Ausführung abschließt), dann ist der Aufruf ein nicht blockierender Aufruf.
  • 🎜Synchron/asynchron: 🎜
    • Wenn B A erst nach Abschluss der Ausführung benachrichtigt, dann ist Dienst BSynchronous;
    • Wenn A B anruft, benachrichtigt B A sofort, dass die Anfrage empfangen wurde, und nachdem die Ausführung abgeschlossen ist, wird sie über callback Das Ausführungsergebnis wird A mitgeteilt, dann ist Dienst B <code>asynchron.
🎜Viele Leute verwechseln blockierend/nicht blockierend oft mit synchron/asynchron, daher ist besondere Aufmerksamkeit erforderlich :🎜
  • Blockierend/nicht blockierend für den Aufrufer des Dienstes
  • Synchron/asynchron code>Für den Angerufenen des Dienstes.
🎜Um blockierend/nicht blockierend und synchron/asynchron zu verstehen, werfen wir einen Blick auf das spezifische E/A-Modell . 🎜

🎜Blockieren von E/A🎜

🎜Definition: Nachdem der Benutzer den I/O-Systemaufruf im (Thread-)Prozess initiiert hat, Der Benutzer-Thread wird sofort blockiert, bis die gesamte E/A-Operation verarbeitet ist und das Ergebnis an den Benutzer-(Thread-)Thread zurückgegeben wird Status blockiert und führen Sie die nachfolgenden Vorgänge weiter aus. 🎜🎜Eigenschaften:🎜
  • Da dieses Modell den (Thread-)Prozess des Benutzers blockiert, belegt dieses Modell keine CPU-Ressourcen
  • Beim Ausführen von E/A Bei dieser Operation kann der Benutzer keine anderen Operationen im (Thread-)Prozess ausführen;
  • Dieses Modell ist nur für Anwendungen mit geringer Parallelität geeignet, da eine I/O-Anfrage eingehende ( Um also rechtzeitig auf I/O-Anfragen reagieren zu können, ist es notwendig, jeder Anfrage einen eingehenden (Thread-)Thread zuzuweisen. Dies führt zu einer enormen Ressourcenauslastung lange Verbindungsanfragen Da beispielsweise die eingehenden (Thread-)Prozessressourcen für längere Zeit nicht freigegeben werden können, kommt es bei zukünftigen neuen Anfragen zu ernsthaften Leistungsengpässen.

🎜Nicht blockierende E/A🎜

🎜Definition: 🎜
  • Initiiert durch den Benutzer-(Thread-)ProzessI/O-Systemaufruf die I/O-Operation nicht bereit ist, gibt der I/O-Aufruf einen Fehler und den Benutzer zurück wird eingeben ( Threads müssen nicht warten, sondern verwenden Polling, um zu erkennen, ob die I/O-Operation bereit ist;
  • Nachdem die Operation bereit ist, wird der tatsächliche I /O Die Operation blockiert den Thread des Benutzers, bis das Ausführungsergebnis an den Thread des Benutzers zurückgegeben wird.
🎜Funktionen: 🎜
  • Da dieses Modell erfordert, dass der Benutzer den I/O-Betriebsbereitschaftsstatus kontinuierlich abfragt (verwenden Sie im Allgemeinen während Code>-Schleife), daher muss dieses Modell die CPU belegen und CPU-Ressourcen verbrauchen;
  • Bevor die E/A-Operation bereit ist, ist der Prozess des Benutzers (Thread) nicht bereit blockiert: Nachdem die I/O-Operation bereit ist, blockiert die nachfolgende tatsächliche I/O-Operation den Benutzer-(Thread-)Prozess;
  • Dieses Modell ist nur anwendbar. Es eignet sich für Anwendungen mit geringer Parallelität, bei denen keine zeitnahe Reaktion erforderlich ist.

🎜Synchronische (asynchrone) E/A🎜

🎜Benutzerprozess initiierter E/A Nach Code>-Systemaufruf Wenn der <code>I/O-Aufruf dazu führt, dass der Thread (Thread) des Benutzers blockiert wird, dann ist der I/O-Aufruf ein synchrones I/O code>, andernfalls <code>asynchrone E/A. 🎜🎜Das Kriterium für die Beurteilung, ob der I/O-Vorgang synchron oder asynchron ist, ist der Fortschritt des Benutzers (Thread) und I/ O Kommunikationsmechanismus für Operationen, wobei: 🎜
  • Synchronisation Im Fall von Benutzerprozessen (Threads) und E/A wird die Interaktion über den Kernelpuffer synchronisiert, d. h. der Kernel wird
Das Ausführungsergebnis der Operation wird mit dem Puffer synchronisiert und dann werden die Daten im Puffer in den Benutzerprozess (Thread) kopiert. Dieser Prozess blockiert den Benutzerprozess (Thread), bis E/A Der Vorgang ist abgeschlossen; 同步情况下用户进(线)程与 I/O 的交互是通过内核缓冲区进行同步的,即内核会将 I/O 操作的执行结果同步到缓冲区,然后再将缓冲区的数据复制到用户进(线)程,这个过程会阻塞用户进(线)程,直到 I/O 操作完成;
  • 异步情况下用户进(线)程与 I/O 的交互是直接通过内核进行同步的,即内核会直接将 I/O 操作的执行结果复制到用户进(线)程,这个过程不会阻塞用户进(线)程。
  • Node.js 的并发模型

    Node.js 采用的是单线程、基于事件驱动的异步 I/O 模型,个人认为之所以选择该模型的原因在于:

    • JavaScript 在 V8 下以单线程模式运行,为其实现多线程极其困难;
    • 绝大多数网络应用都是 I/O 密集型的,在保证高并发的情况下,如何合理、高效地管理多线程资源相对于单线程资源的管理更加复杂。

    总之,本着简单、高效的目的,Node.js 采用了单线程、基于事件驱动的异步 I/O 模型,并通过主线程的 EventLoop 和辅助的 Worker 线程来实现其模型:

    • Node.js 进程启动后,Node.js 主线程会创建一个 EventLoop,EventLoop 的主要作用是注册事件的回调函数并在未来的某个事件循环中执行;
    • Worker 线程用来执行具体的事件任务(在主线程之外的其它线程中以同步方式执行),然后将执行结果返回到主线程的 EventLoop 中,以便 EventLoop 执行相关事件的回调函数。

    需要注意的是,Node.js 并不适合执行 CPU 密集型(即需要大量计算)任务;这是因为 EventLoop 与 JavaScript 代码(非异步事件任务代码)运行在同一线程(即主线程),它们中任何一个如果运行时间过长,都可能导致主线程阻塞,如果应用程序中包含大量需要长时间执行的任务,将会降低服务器的吞吐量,甚至可能导致服务器无法响应。

    总结

    Node.js 是前端开发人员现在乃至未来不得不面对的技术,然而大多数前端开发人员对 Node.js 的认知仅停留在表面,为了让大家更好地理解 Node.js 的并发模型,本文先介绍了进程、线程、协程,接着介绍了不同的 I/OAsynchron Im Fall von Benutzerprozessen (Threads) und E/A wird die Interaktion direkt über den Kernel synchronisiert , kopiert der Kernel das Ausführungsergebnis der I/O-Operation direkt in den Benutzer-Thread. Dieser Prozess blockiert den Benutzer-Thread nicht.

    Das Parallelitätsmodell von Node.js

    Node.js verwendet ein Single-Threaded, ereignisgesteuertes The asynchrones I/O-Modell Ich persönlich denke, der Grund für die Wahl dieses Modells ist:

    JavaScript läuft im Single-Thread-Modus unter V8 und es ist äußerst schwierig, Multi-Threading zu implementieren /li> Die überwiegende Mehrheit der Netzwerkanwendungen ist E/A-intensiv. Die vernünftige und effiziente Verwaltung von Multithread-Ressourcen bei gleichzeitiger Sicherstellung einer hohen Parallelität ist komplizierter als die Verwaltung von Single-Thread-Ressourcen. Kurz gesagt, aus Gründen der Einfachheit und Effizienz verwendet Node.js ein ereignisgesteuertes asynchrones I/O-Modell mit einem Thread und verwendet die des Hauptthreads EventLoop und Hilfs-Worker-Threads zur Implementierung seines Modells:

    🎜🎜Nachdem der Node.js-Prozess gestartet wurde, erstellt der Node.js-Hauptthread einen EventLoop. Die Hauptfunktion des EventLoop besteht darin, die Rückruffunktion des Ereignisses zu registrieren und auszuführen ;🎜Der Worker-Thread wird verwendet, um bestimmte Ereignisaufgaben auszuführen (die synchron in anderen Threads als dem Haupt-Thread ausgeführt werden) und gibt dann die Ausführungsergebnisse an die EventLoop des Haupt-Threads zurück dass die EventLoop Rückrufe für verwandte Ereignisse ausführen kann. 🎜Es ist zu beachten, dass Node.js nicht für die Ausführung CPU-intensiver (d. h. viele Berechnungen erfordernder) Aufgaben geeignet ist, da EventLoop- und JavaScript-Code (nicht asynchroner Ereignisaufgabencode) vorhanden sind. Wenn einer von ihnen zu lange ausgeführt wird, kann dies dazu führen, dass der Hauptthread blockiert wird. Wenn die Anwendung eine große Anzahl von Aufgaben enthält, die eine lange Ausführung erfordern, verringert sich der Durchsatz des Servers und kann sogar dazu führen, dass der Server nicht mehr reagiert. 🎜

    Zusammenfassung

    🎜Node.js ist eine Technologie, mit der sich Frontend-Entwickler jetzt und auch in Zukunft auseinandersetzen müssen Front-End-Entwickler sind nicht mit dem Verständnis von Node.js vertraut. Damit jeder das Parallelitätsmodell von Node.js besser verstehen kann, werden in diesem Artikel zunächst Prozesse, Threads und Coroutinen vorgestellt I/O Modell und schließlich eine kurze Einführung in das Parallelitätsmodell von Node.js. Obwohl eingeführt Es gibt nicht viel Platz im Parallelitätsmodell von Node.js, aber ich glaube, dass es sich nie ändern wird, ohne von seinen Wurzeln abzuweichen. Sobald Sie die relevanten Grundlagen beherrschen und dann das Design und die Implementierung von Node.js tiefgreifend verstehen, werden Sie doppelt so viel bekommen Das Ergebnis mit halbem Aufwand. 🎜🎜Wenn in diesem Artikel Fehler enthalten sind, hoffe ich, dass Sie diese korrigieren können. Ich wünsche Ihnen allen viel Spaß beim Codieren. 🎜🎜Weitere Informationen zu Knoten finden Sie unter: 🎜nodejs-Tutorial🎜! 🎜

    Das obige ist der detaillierte Inhalt vonLassen Sie uns über Prozesse, Threads, Coroutinen und Parallelitätsmodelle in Node.js sprechen. Für weitere Informationen folgen Sie bitte anderen verwandten Artikeln auf der PHP chinesischen Website!

    Stellungnahme:
    Dieser Artikel ist reproduziert unter:juejin.cn. Bei Verstößen wenden Sie sich bitte an admin@php.cn löschen