Heim > Artikel > Web-Frontend > 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.
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:
进程表
的表格,每个进程占用一个进程表项
(也叫进程控制块
, 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:
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:
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:
Manchmal müssen wir Threads verwenden, um die folgenden Probleme zu lösen:
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;
können auf alle gemeinsam genutzten Adressräume und Systemressourcen des Prozesses zugreifen, zu dem sie gehören.
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; }
updateUserName Die logische Die Ausführungssequenz innerhalb von Code> und <code>updateUserNameAsync
ist:
getUserById
auf und weisen Sie ihren Rückgabewert der Variablen user
zu. Aufruf die updateName
-Methode von user
true
an den Aufrufer zurück. updateUserName
wird diese der Reihe nach gemäß der oben genannten logischen Reihenfolge ausgeführt; async/await
便是协程的一种实现,比如下面的例子:
上例中,函数 updateUserName
和 updateUserNameAsync
内的逻辑执行顺序是:
getUserById
并将其返回值赋给变量 user
;user
的 updateName
方法;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.
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.
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 就是异步
的。很多人经常将阻塞/非阻塞
与同步/异步
搞混淆,故需要特别注意:
阻塞/非阻塞
针对于服务的调用者
而言;同步/异步
针对于服务的被调用者
而言。了解了阻塞/非阻塞
与同步/异步
,我们来看具体的 I/O 模型
。
定义:用户进(线)程发起 I/O
系统调用后,用户进(线)程会被立即阻塞
,直到整个 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
-Operationsanforderung;I/O
-Operationsanforderung (unterteilt in Vorbereitungsphase und tatsächliche Ausführungsphase) und gibt sie zurück Das Verarbeitungsergebnis gibt Benutzern einen (Thread-)Prozess. 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
: 🎜 Blockierender Aufruf
;nicht blockierender Aufruf
. Synchron/asynchron
: 🎜callback Das Ausführungsergebnis wird A mitgeteilt, dann ist Dienst B <code>asynchron
.
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. blockierend/nicht blockierend
und synchron/asynchron
zu verstehen, werfen wir einen Blick auf das spezifische E/A-Modell
. 🎜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:🎜E/A
Bei dieser Operation kann der Benutzer keine anderen Operationen im (Thread-)Prozess ausführen;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. 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;I /O
Die Operation blockiert den Thread des Benutzers, bis das Ausführungsergebnis an den Thread des Benutzers zurückgegeben wird. I/O
-Betriebsbereitschaftsstatus kontinuierlich abfragt (verwenden Sie im Allgemeinen während Code>-Schleife), daher muss dieses Modell die CPU belegen und CPU-Ressourcen verbrauchen;
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;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 E/A
Der Vorgang ist abgeschlossen; 同步
情况下用户进(线)程与 I/O
的交互是通过内核缓冲区进行同步的,即内核会将 I/O
操作的执行结果同步到缓冲区,然后再将缓冲区的数据复制到用户进(线)程,这个过程会阻塞用户进(线)程,直到 I/O
操作完成;异步
情况下用户进(线)程与 I/O
的交互是直接通过内核进行同步的,即内核会直接将 I/O
操作的执行结果复制到用户进(线)程,这个过程不会阻塞用户进(线)程。Node.js 采用的是单线程、基于事件驱动的异步 I/O
模型,个人认为之所以选择该模型的原因在于:
I/O
密集型的,在保证高并发的情况下,如何合理、高效地管理多线程资源相对于单线程资源的管理更加复杂。总之,本着简单、高效的目的,Node.js 采用了单线程、基于事件驱动的异步 I/O
模型,并通过主线程的 EventLoop 和辅助的 Worker 线程来实现其模型:
需要注意的是,Node.js 并不适合执行 CPU 密集型(即需要大量计算)任务;这是因为 EventLoop 与 JavaScript 代码(非异步事件任务代码)运行在同一线程(即主线程),它们中任何一个如果运行时间过长,都可能导致主线程阻塞,如果应用程序中包含大量需要长时间执行的任务,将会降低服务器的吞吐量,甚至可能导致服务器无法响应。
Node.js 是前端开发人员现在乃至未来不得不面对的技术,然而大多数前端开发人员对 Node.js 的认知仅停留在表面,为了让大家更好地理解 Node.js 的并发模型,本文先介绍了进程、线程、协程,接着介绍了不同的 I/O
Asynchron
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.
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:
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!