Programmation asynchrone de Node.js: compréhension approfondie des boucles d'événements
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.
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.
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:
setTimeout( () => console.log('Hi from the callback queue'), 5000); // 保持循环活动这么长时间 const stopTime = Date.now() + 2000; while (Date.now() < stopTime) {}
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.
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:
const stopTime = Date.now() + 2000; while (Date.now() < stopTime) {} // 这需要 7 秒才能执行 setTimeout(() => console.log('Ran callback A'), 5000);
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
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
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...');
, 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
Maintenant, je veux vous emmener plus profondément dans Node.js.
Ce sont les phases de boucle d'événements:
Source de l'image: Document Libuv
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:
setTimeout( () => console.log('Hi from the callback queue'), 5000); // 保持循环活动这么长时间 const stopTime = Date.now() + 2000; while (Date.now() < stopTime) {}
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.
pour afficher chaque étape avec un javascript pur:
const stopTime = Date.now() + 2000; while (Date.now() < stopTime) {} // 这需要 7 秒才能执行 setTimeout(() => console.log('Ran callback A'), 5000);
É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:
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...');
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.
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:
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.
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.
À 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 ():
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é.
setTimeout( () => console.log('Hi from the callback queue'), 5000); // 保持循环活动这么长时间 const stopTime = Date.now() + 2000; while (Date.now() < stopTime) {}
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.
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.
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!