Maison >interface Web >js tutoriel >La boucle d'événement Node.js: un Guide des développeurs sur les concepts et le code

La boucle d'événement Node.js: un Guide des développeurs sur les concepts et le code

Christopher Nolan
Christopher Nolanoriginal
2025-02-12 08:36:12575parcourir

Programmation asynchrone de Node.js: compréhension approfondie des boucles d'événements

The Node.js Event Loop: A Developer's Guide to Concepts & Code

La programmation asynchrone est extrêmement difficile dans tout langage de programmation. Des concepts tels que la concurrence, le parallélisme et l'impasse rendent même les ingénieurs les plus expérimentés. Le code exécuté de manière asynchrone est difficile à prévoir et difficile à suivre en cas de bug. Cependant, ce problème est inévitable, car l'informatique moderne a des processeurs multicœurs. Chaque noyau de processeur a sa propre limite thermique, et l'amélioration des performances monocRE a atteint un goulot d'étranglement. Cela incite les développeurs à écrire du code efficace et à utiliser pleinement les ressources matérielles.

JavaScript est unique, mais cela limite-t-il la capacité de Node.js à profiter des architectures modernes? L'un des plus grands défis consiste à faire face à la complexité inhérente du multithreading. La création d'un nouveau thread et la gestion de la commutation de contexte entre les threads coûtent cher. Le système d'exploitation et les programmeurs ont besoin de beaucoup d'efforts pour fournir une solution qui gère de nombreux cas de bord. Cet article expliquera comment Node.js résout ce problème à travers des boucles d'événements, explore divers aspects des boucles d'événements Node.js et montre comment cela fonctionne. Les boucles d'événements sont l'une des fonctionnalités de tueur de Node.js car elle résout ce problème délicat d'une manière complètement nouvelle.

points clés

  • La boucle d'événement Node.js est une boucle simultanée unique, non bloquée et asynchrone qui permet de traiter efficacement plusieurs tâches sans attendre que chaque tâche se termine. Cela permet de traiter simultanément plusieurs demandes Web.
  • La boucle d'événement est semi-infinie, ce qui signifie qu'elle peut quitter si la pile d'appels ou la file d'attente de rappel est vide. Cette boucle est responsable de l'interrogation du système d'exploitation pour obtenir des rappels à partir de connexions entrantes.
  • La boucle d'événement s'exécute en plusieurs étapes: mise à jour de l'horodatage, vérification de l'activité de boucle, exécution du temporisation, exécution de rappel en attente, exécution du gestionnaire d'inactivité, préparation de la poignée pour l'exécution de rappel SetMmediate, calculer le délai d'expiration du sondage, bloquer les E / S, vérifier l'exécution de rappel de la poignée, fermer l'exécution, fermeture Exécution de rappel et itération de fin.
  • Node.js utilise deux pièces principales: le moteur JavaScript V8 et Libuv. Les E / S de réseau, les E / S de fichiers et les requêtes DNS sont effectuées via Libuv. Le nombre de threads disponibles pour ces tâches dans le pool de threads est limité et peut être réglé via la variable d'environnement UV_THERDPOOL_SIZE.
  • À la fin de chaque étape, la boucle exécute le rappel Process.NextTick (), qui ne fait pas partie de la boucle d'événement, car il fonctionne à la fin de chaque étape. Le rappel SETIMMEDIAD () fait partie de toute la boucle d'événement, il n'est donc pas exécuté immédiatement comme le nom l'indique. Il est généralement recommandé d'utiliser SETIMMEDIAD ().

Qu'est-ce qu'une boucle d'événement?

La boucle d'événement est une boucle simultanée unique, non bloconneuse et asynchrone. Pour quelqu'un sans diplôme en informatique, imaginez une demande Web qui effectue des recherches de base de données. Un seul thread ne peut effectuer qu'une seule opération à la fois. Au lieu d'attendre que la base de données réponde, elle continue de traiter d'autres tâches dans la file d'attente. Dans la boucle d'événement, la boucle principale étend la pile d'appels et n'attend pas le rappel. Étant donné que la boucle ne bloque pas, elle peut gérer plusieurs demandes Web simultanément. Plusieurs demandes peuvent être en file d'attente en même temps, ce qui les rend simultanées. La boucle n'attend pas toutes les opérations d'une demande à terminer, mais est traitée en fonction de l'ordre dans lequel le rappel se produit sans blocage.

La boucle elle-même est semi-infinie, ce qui signifie qu'elle peut quitter la boucle si la pile d'appels ou la file d'attente de rappel est vide. La pile d'appels peut être considérée comme un code synchrone, tel que Console.log, en expansion avant de boucler pour interroger plus de travail. Node.js utilise le libuv sous-jacent pour interroger le système d'exploitation pour les rappels à partir de connexions entrantes.

Vous vous demandez peut-être pourquoi les boucles d'événements sont exécutées dans un seul fil? Les threads sont relativement plus lourds en mémoire pour les données requises pour chaque connexion. Les threads sont des ressources du système d'exploitation qui doivent être démarrées, qui ne peuvent pas être étendues à des milliers de connexions actives.

Habituellement, le multithreading peut également compliquer la situation. Si le rappel renvoie les données, elle doit rassembler le contexte au thread exécuté. La commutation de contexte entre les threads est lente car elle doit synchroniser l'état actuel, comme la pile d'appels ou les variables locales. Les boucles d'événements peuvent éviter les bogues lorsque plusieurs threads partagent des ressources car il est unique. Les boucles à thread unique réduisent les cas de bord filettes et permettent une commutation de contexte plus rapide. C'est le vrai génie derrière la boucle. Il utilise efficacement les connexions et les threads tout en maintenant l'évolutivité.

La théorie est suffisante; voyons maintenant à quoi ressemble le code. Vous pouvez le faire dans REPOT comme vous le souhaitez ou télécharger le code source.

boucle semi-infinie

La plus grande question à laquelle les boucles d'événements doivent répondre est de savoir si la boucle est active. Si c'est le cas, déterminez combien de temps attendre la file d'attente de rappel. Dans chaque itération, Loop étend la pile d'appels et les sondages.

Ceci est un exemple de blocage de la boucle principale:

<code class="language-javascript">setTimeout(
  () => console.log('Hi from the callback queue'),
  5000); // 保持循环活动这么长时间

const stopTime = Date.now() + 2000;
while (Date.now() < stopTime) {}</code>

Si vous exécutez ce code, notez que la boucle est bloquée pendant deux secondes. Cependant, la boucle reste active jusqu'à ce que le rappel soit exécuté après cinq secondes. Une fois la boucle principale débloquée, le mécanisme de sondage détermine combien de temps il attendra le rappel. Cette boucle se termine lorsque la pile d'appels se développe et il n'y a plus de rappels.

file d'attente de rappel

Maintenant, que se passe-t-il lorsque je bloque la boucle principale, puis planifie le rappel? Une fois la boucle bloquée, elle n'ajoute pas plus de rappels à la file d'attente:

<code class="language-javascript">const stopTime = Date.now() + 2000;
while (Date.now() < stopTime) {}
// 这需要 7 秒才能执行
setTimeout(() => console.log('Ran callback A'), 5000);</code>

Ce cycle reste actif pendant sept secondes. Les boucles d'événements sont stupides en termes de simplicité. Il n'a aucun moyen de savoir ce qui pourrait faire la queue à l'avenir. Dans les systèmes réels, les rappels entrants sont en file d'attente et exécutés lorsque la boucle principale peut être interrogée. La boucle d'événement passe par plusieurs étapes de séquence lors du déblocage. Donc, pour se démarquer dans une interview sur les boucles, évitez les termes de fantaisie comme le «lanceur d'événements» ou le «mode réacteur». Il s'agit d'une boucle simple à thread unique, simultanée et non bloquante. boucle d'événements en utilisant async / attend

Pour éviter de bloquer la boucle principale, une idée est d'envelopper des E / S synchrones avec asynchrone / attendre:

Tout ce qui apparaît après attendre vient de la file d'attente de rappel. Le code ressemble à un code de blocage synchrone, mais il ne bloque pas. Notez qu'Async / Await fait readFileSync a
<code class="language-javascript">const fs = require('fs');
const readFileSync = async (path) => await fs.readFileSync(path);

readFileSync('readme.md').then((data) => console.log(data));
console.log('The event loop continues without blocking...');</code>
thereable

, qui le supprime de la boucle principale. Tout ce qui apparaît après attendre peut être considéré comme une opération non bloquante via un rappel. Divulgation complète: le code ci-dessus est à des fins de démonstration uniquement. Dans le code réel, je recommande d'utiliser Fs.Readfile, qui déclenche un rappel qui peut être enroulé autour de Promise. L'intention globale reste valide car cela bloquera l'élimination des E / S de la boucle principale.

aller un peu plus loin

Et si je vous disais que les boucles d'événements ne sont pas seulement des piles d'appels et des files d'attente de rappel? Et si une boucle d'événement n'est pas seulement une boucle, mais plusieurs boucles? Et s'il pouvait avoir plusieurs threads en bas?

Maintenant, je veux vous emmener plus profondément dans Node.js.

Étape de boucle d'événement

Ce sont les phases de boucle d'événements:

The Node.js Event Loop: A Developer's Guide to Concepts & Code

Source de l'image: Document Libuv

  1. Mettre à jour l'horodatage. La boucle d'événements cache l'heure actuelle au début de la boucle pour éviter les appels système de temps fréquents. Ces appels système sont des appels internes à Libuv.
  2. La boucle est-elle active? Si la boucle a une poignée active, une demande active ou une poignée fermée, elle est active. Comme indiqué, le rappel en attente dans la file d'attente maintient la boucle active.
  3. Exécuter la minuterie d'expiration. C'est là que le rappel Settimeout ou SetInterval fonctionne. Loops pour vérifier en cache maintenant pour permettre l'exécution des rappels actifs expirés.
  4. Exécuter des rappels en attente dans la file d'attente. Si des rappels étaient retardés par les itérations précédentes, ces rappels fonctionneront pour le moment. Le sondage exécute généralement le rappel d'E / S immédiatement, à des exceptions. Cette étape gère les rappels décalés de la dernière itération.
  5. Exécuter des gestionnaires d'inactivité - principalement en raison de la dénomination incorrecte, car ces gestionnaires fonctionnent dans chaque itération et sont des gestionnaires internes pour libuv.
  6. Préparez-vous à exécuter la poignée du rappel SetImMediate dans l'itération de la boucle. Ces poignées fonctionnent avant la boucle bloque les E / S et préparent une file d'attente pour ce type de rappel.
  7. Calculer le délai d'expiration du sondage. La boucle doit savoir quand elle bloque les E / S. C'est ainsi qu'il calcule le délai d'expiration:
    • Si la boucle est sur le point de quitter, le délai d'attente est 0.
    • S'il n'y a pas de poignée ou de demande active, le délai d'attente est 0.
    • S'il y a des poignées libres, le délai d'attente est 0.
    • S'il y a des poignées en attente dans la file d'attente, le délai d'attente est 0.
    • S'il existe des poignées fermées, le délai d'expiration est 0.
    • Si aucun de ces éléments n'est, le délai d'expiration est défini sur la minuterie la plus proche, et s'il n'y a pas de minuteries actifs, c'est infinie .
  8. cyclisage de la durée de l'étape précédente bloque les E / S. Les rappels liés aux I / O dans la file d'attente sont exécutés ici.
  9. Exécutez le rappel de vérification de la manche. Cette étape est le stade de la course à la course Settimmediate, qui est l'étape correspondante pour la préparation de la poignée. Tout rappel SETIMMEDIADE que la file d'attente pendant l'exécution de rappel d'E / S s'exécutera ici.
  10. Exécutez le rappel fermé. Ce sont les poignées actives libérées de la connexion fermée.
  11. itération se termine.

Vous vous demandez peut-être pourquoi les collisions sont des E / S alors qu'elle ne devrait pas bloquer? La boucle ne bloquera que lorsqu'il n'y a pas de rappels en attente dans la file d'attente et que la pile d'appels est vide. Dans Node.js, le temporisateur le plus proche peut être défini via Settimeout, par exemple. S'il est réglé sur Infinite, la boucle attendra que les connexions entrantes fassent plus de travail. Il s'agit d'une boucle semi-infinie car le sondage maintient la boucle active lorsqu'il n'y a pas de travail restant et qu'il y a une connexion active.

Ce qui suit est la version UNIX de ce calcul de délai d'expiration, dans toute sa forme de code C:

<code class="language-javascript">setTimeout(
  () => console.log('Hi from the callback queue'),
  5000); // 保持循环活动这么长时间

const stopTime = Date.now() + 2000;
while (Date.now() < stopTime) {}</code>

Vous ne connaissez peut-être pas très bien C, mais cela se lit comme l'anglais et fait exactement comme décrit dans la phase 7.

Démonstration phase par étape

pour afficher chaque étape avec un javascript pur:

<code class="language-javascript">const stopTime = Date.now() + 2000;
while (Date.now() < stopTime) {}
// 这需要 7 秒才能执行
setTimeout(() => console.log('Ran callback A'), 5000);</code>

Étant donné que le rappel des E / S du fichier s'exécute avant les étages 4 et 9, il est prévu que SetImMmediat () se déclenche d'abord:

<code class="language-javascript">const fs = require('fs');
const readFileSync = async (path) => await fs.readFileSync(path);

readFileSync('readme.md').then((data) => console.log(data));
console.log('The event loop continues without blocking...');</code>

Les E / S du réseau sans requêtes DNS sont moins chères que les E / S de fichiers car elle s'exécute dans la boucle de l'événement principal. Les E / S de fichiers sont en file d'attente via le pool de threads. Les requêtes DNS utilisent également des pools de threads, ce qui rend les E / S de réseau aussi chères que les E / S de fichiers.

Pool de thread

Node.js a deux pièces principales: le moteur JavaScript V8 et Libuv. Les E / S de fichiers, la requête DNS et les E / S de réseau sont effectuées via Libuv.

C'est la structure globale:

The Node.js Event Loop: A Developer's Guide to Concepts & Code

Source de l'image: Document Libuv

Pour les E / S de réseau, le sondage sur les boucles d'événements dans le thread principal. Ce thread n'est pas un thread-safe car il n'a pas de commutateurs de contexte avec un autre thread. Les requêtes E / S de fichiers et DNS sont spécifiques à la plate-forme, la méthode consiste donc à les exécuter dans un pool de threads. Une idée est de faire des requêtes DNS vous-même pour éviter d'entrer dans le pool de threads, comme indiqué dans le code ci-dessus. Par exemple, la saisie d'une adresse IP au lieu de LocalHost supprime la recherche du pool. Le nombre de threads disponibles dans le pool de threads est limité et peut être défini via la variable d'environnement UV_THREADPOOL_SIZE. La taille du pool de thread par défaut est d'environ quatre.

V8 est exécuté dans une boucle séparée, efface la pile d'appels et renvoie le contrôle à la boucle d'événement. V8 peut utiliser plusieurs fils pour la collecte des ordures en dehors de sa propre boucle. Considérez le V8 comme un moteur qui prend le JavaScript d'origine et l'exécute sur le matériel.

Pour les programmeurs ordinaires, JavaScript reste unique car il n'y a pas de problèmes de sécurité de fil. V8 et Libuv démarrent en interne leurs propres fils séparés pour répondre à leurs propres besoins.

S'il y a un problème de débit dans Node.js, commencez par la boucle de l'événement principal. Vérifiez combien de temps il faut pour qu'une application termine une seule itération. Il ne devrait pas dépasser cent millisecondes. Ensuite, vérifiez la faim de la piscine de fil et ce qui peut être expulsé de la piscine. La taille de la piscine peut également être augmentée par les variables environnementales. La dernière étape consiste à effectuer un microbencharement du code JavaScript dans le V8 exécuté de manière synchrone.

Résumé

La boucle d'événement continue d'itérer sur chaque étape car le rappel est en file d'attente. Cependant, dans chaque étape, il existe des moyens de faire la queue d'un autre type de rappel.

process.NextTick () et setImMmediate ()

À la fin de chaque étape, le rappel Process.NextTick () est exécuté dans une boucle. Notez que ce type de rappel ne fait pas partie de la boucle d'événement, car il s'exécute à la fin de chaque étape. Le rappel SETIMMEDIAD () fait partie de toute la boucle d'événement, il n'est donc pas exécuté immédiatement comme le nom l'indique. Puisque process.NextTick () nécessite une compréhension du mécanisme interne des boucles d'événements, je recommande généralement d'utiliser SetImMediat ().

Plusieurs raisons pour lesquelles vous pourriez avoir besoin de processus.NextTick ():

  1. Autoriser les E / S du réseau pour gérer les erreurs, nettoyer ou réessayer les demandes avant la poursuite de la boucle.
  2. Il peut être nécessaire d'exécuter le rappel une fois la pile d'appel étendue mais avant la poursuite de la boucle.

Par exemple, un émetteur d'événements souhaite déclencher un événement dans son propre constructeur. La pile d'appels doit être élargie avant que l'événement puisse être appelé.

<code class="language-javascript">setTimeout(
  () => console.log('Hi from the callback queue'),
  5000); // 保持循环活动这么长时间

const stopTime = Date.now() + 2000;
while (Date.now() < stopTime) {}</code>

Autoriser l'expansion de la pile d'appels empêche des erreurs telles que RangeError: la taille maximale de la pile d'appels dépassée. Une chose à noter est de s'assurer que Process.NextTick () ne bloque pas la boucle d'événement. Les appels de rappel récursifs dans la même étape peuvent entraîner des problèmes de blocage.

Conclusion

La boucle d'événement incarne la simplicité dans sa complexité ultime. Il résout un problème difficile comme l'asynchronicité, la sécurité des fils et la concurrence. Il supprime les pièces inutiles ou indésirables et maximise le débit de la manière la plus efficace. Par conséquent, les programmeurs Node.js peuvent réduire le temps pour poursuivre les erreurs asynchrones et passer plus de temps à fournir de nouvelles fonctionnalités.

FAQ sur les boucles d'événement Node.js

Quelle est la boucle d'événement Node.js? La boucle d'événement Node.js est le mécanisme central qui permet à Node.js d'effectuer des opérations asynchrones non bloquantes. Il est responsable de la gestion des opérations d'E / S, des minuteries et des rappels dans un environnement unique sur les événements.

Comment fonctionne la boucle d'événement Node? La boucle d'événements vérifie en continu des événements ou des rappels dans la file d'attente d'événements et les exécute dans l'ordre d'addition. Il s'exécute dans une boucle, manipulant des événements basés sur la disponibilité des événements, ce qui rend possible la programmation asynchrone dans Node.js.

Quel est le rôle des boucles d'événements dans les applications Node.js? Les boucles d'événements sont au cœur de Node.js, ce qui garantit que les applications restent réactives et peuvent gérer de nombreuses connexions simultanées sans threads multiples.

Quelles sont les étapes de la boucle d'événement Node.js? La boucle d'événements dans Node.js a plusieurs étapes, notamment la minuterie, les rappels en attente, le ralenti, le sondage, la vérification et la fermeture. Ces phases déterminent comment et commander les événements sont traités.

Quels sont les types d'événements les plus courants qui sont traités par des boucles d'événements? Les événements communs incluent les opérations d'E / S (par exemple, la lecture d'un fichier ou la publication d'une demande de réseau), les minuteries (par exemple, setTimeout et SetInterval) et les fonctions de rappel (par exemple, les rappels à partir d'opérations asynchrones).

NODE Comment gérer les opérations à long terme en boucles d'événements? Les opérations à longue durée de processeur peuvent bloquer la boucle d'événement et doivent être déchargées dans des processus enfants ou des threads de travail à l'aide de modules tels que les modules Child_Process ou Worker_Threads.

Quelle est la différence entre une pile d'appels et une boucle d'événement? La pile d'appels est une structure de données qui suit les appels de fonction dans le contexte d'exécution actuel, tandis que la boucle d'événement est responsable de la gestion des opérations asynchrones et non bloquant. Ils travaillent ensemble parce que la boucle d'événement planifie l'exécution des rappels et des opérations d'E / S, puis les pousse à la pile d'appels.

Qu'est-ce que la "tick" dans la boucle d'événement? "Tick" fait référence à une seule itération de la boucle d'événement. Dans chaque tique, la boucle d'événement vérifie les événements en attente et exécute tous les rappels prêts à s'exécuter. Ticks est l'unité de travail de base dans une application Node.js.

Ce qui précède est le contenu détaillé de. pour plus d'informations, suivez d'autres articles connexes sur le site Web de PHP en chinois!

Déclaration:
Le contenu de cet article est volontairement contribué par les internautes et les droits d'auteur appartiennent à l'auteur original. Ce site n'assume aucune responsabilité légale correspondante. Si vous trouvez un contenu suspecté de plagiat ou de contrefaçon, veuillez contacter admin@php.cn