Maison  >  Article  >  interface Web  >  Explication détaillée du flux de travail et du cycle de vie de la boucle d'événements Node.js

Explication détaillée du flux de travail et du cycle de vie de la boucle d'événements Node.js

不言
不言original
2018-08-15 14:26:472223parcourir

Cet article vous apporte une explication détaillée du flux de travail de la boucle d'événements et du cycle de vie de Node.js. Il a une certaine valeur de référence. Les amis dans le besoin peuvent s'y référer.

Cet article expliquera en détail le workflow et le cycle de vie de la boucle d'événement node.js

Quelques malentendus courants

dans js L'événement boucle à l'intérieur du moteur

L'un des malentendus les plus courants est que la boucle d'événements fait partie du moteur Javascript (V8, spiderMonkey, etc.). En fait, la boucle d’événements utilise principalement le moteur Javascript pour exécuter du code.

Il y a une pile ou une file d'attente

Tout d'abord, il n'y a pas de pile. Deuxièmement, ce processus est compliqué, avec plusieurs files d'attente (comme les files d'attente dans la structure des données. ) impliqué. Mais la plupart des développeurs savent combien de fonctions de rappel sont regroupées dans une seule file d'attente, ce qui est complètement faux.

La boucle d'événements s'exécute dans un thread séparé

En raison du mauvais diagramme de boucle d'événements node.js, certains d'entre nous pensaient que vous aviez deux threads. L'un exécute Javascript et l'autre exécute la boucle d'événements. En fait, ils fonctionnent tous dans un seul thread.

Il existe une participation asynchrone du système d'exploitation à setTimeout

Un autre très gros malentendu est que la fonction de rappel de setTimeout est appelée une fois le délai donné écoulé (peut-être le système d'exploitation ou le noyau ) avance une file d'attente.

setImmediate met la fonction de rappel en première position

En tant que description courante de boucle d'événement, il n'y a qu'une seule file d'attente, donc certains développeurs pensent que setImmediate place le rappel dans le le travail fait la queue devant. C'est complètement faux. Les files d'attente de travail en Javascript sont premier entré, premier sorti

L'architecture de la boucle d'événements

Lorsque nous commençons à décrire le flux de travail de la boucle événementielle, il est très important de connaître son architecture. La figure suivante montre le flux de travail réel de la boucle d'événements :

Explication détaillée du flux de travail et du cycle de vie de la boucle dévénements Node.js

Les différentes cases de la figure représentent différentes étapes, et chaque étape effectue un travail spécifique. Chaque étape a une file d'attente (on dit ici qu'il s'agit d'une file d'attente principalement pour une meilleure compréhension ; la structure réelle des données peut ne pas être une file d'attente), et Javascript peut être exécuté à n'importe quelle étape (sauf inactivité et préparation). Vous pouvez également voir nextTickQueue et microTaskQueue dans l'image. Ils ne font pas partie de la boucle et leurs rappels peuvent être exécutés à tout moment. Ils ont une priorité plus élevée à exécuter.

Vous savez maintenant que la boucle d'événement est une combinaison de différentes étapes et de différentes files d'attente. Vous trouverez ci-dessous une description de chaque étape ;

Phase du timer

C'est la phase au début de la boucle d'événement. La file d'attente liée à cette phase conserve le timer (setTimeout, setInterval) Le rappel, bien que. il ne pousse pas le rappel dans la file d'attente, utilise un tas minimal pour maintenir le minuteur et exécute le rappel une fois l'événement spécifié atteint.

Phase de rappel d'E/S en attente

Cette phase exécute les rappels dans la file d'attente en attente dans la boucle d'événements. Par exemple, lorsque vous essayez d'écrire quelque chose sur TCP, le travail est terminé et le rappel est placé dans la file d'attente. Les rappels pour la gestion des erreurs sont également ici.

Inactif, Phase de préparation

Même si le nom est inactif, il s'exécute à chaque tick. La préparation s'exécute également avant le début de la phase d'interrogation. Quoi qu'il en soit, ces deux étapes sont les étapes où le nœud effectue principalement certaines opérations internes.

Phase du sondage

La phase la plus importante de toute la boucle de l'événement est peut-être la phase du sondage. Cette phase accepte les nouvelles connexions entrantes (établissement d'un nouveau Socket, etc.) et les données (lecture de fichiers, etc.). Nous pouvons diviser la phase de vote en plusieurs parties différentes.

  1. S'il y a des éléments dans la watch_queue (la file d'attente liée à la phase d'interrogation), ils seront exécutés les uns après les autres jusqu'à ce que la file d'attente soit vide ou que le système atteigne la limite maximale .

  2. Une fois la file d'attente vide, le nœud attendra de nouvelles connexions. L'événement d'attente ou de sommeil dépend de divers facteurs.

Phase de vérification

La phase suivante de l'interrogation est la phase de vérification, qui est dédiée à setImmediate. Pourquoi avez-vous besoin d’une file d’attente dédiée pour gérer les rappels setImmediate ? Cela est dû au comportement de la phase d'interrogation, qui sera abordé plus loin dans la section processus. Pour l'instant, rappelez-vous simplement que la phase de vérification gère principalement le rappel setImmediate().

Fermer le rappel

La fermeture du rappel (stocket.on('close', () => {})) est entièrement gérée ici, plus comme une phase de nettoyage.

nextTickQueue & microTaskQueue

Les tâches de nextTickQueue sont conservées dans les rappels déclenchés par process.nextTick(). microTaskQueue conserve les rappels déclenchés par Promise. Aucun d'entre eux ne fait partie de la boucle d'événements (non développée en libUV), mais en node. Lorsque C/C++ et Javascript se croisent, ils sont appelés le plus rapidement possible. Par conséquent, ils doivent être exécutés après l’exécution de l’opération en cours (pas nécessairement après l’exécution du rappel js actuel).

Workflow de boucle d'événement

Lors de l'exécution de node my-script.js dans votre console, node configure la boucle d'événement puis exécute votre module principal (my-script. js) En dehors de la boucle d'événement. Une fois le module principal exécuté, le nœud vérifiera si la boucle est toujours active (y a-t-il quelque chose à faire dans la boucle d'événements) ? Sinon, il se terminera après avoir exécuté le rappel de sortie. process, on('exit', foo) rappel (rappel de sortie). Mais si la boucle est toujours active, le nœud entrera dans la boucle à partir de la phase de minuterie.

Explication détaillée du flux de travail et du cycle de vie de la boucle dévénements Node.js

Flux de travail de la phase de minuterie

La boucle d'événements entre dans la phase de minuterie et vérifie s'il y a quelque chose dans la file d'attente du minuteur qui doit être exécuté. D'accord, cela semble très simple, mais la boucle d'événements doit en réalité effectuer quelques étapes pour trouver le rappel approprié. En fait, les scripts de minuterie sont stockés dans la mémoire tas par ordre croissant. Il obtient d'abord un minuteur d'exécution et calcule si now-registeredTime == delta ? Si tel est le cas, il exécutera le rappel du minuteur et vérifiera le minuteur suivant. Jusqu'à ce qu'il trouve un minuteur qui n'a pas encore été programmé, il cessera de vérifier les autres minuteurs (car les minuteurs sont classés par ordre croissant) et passera à l'étape suivante.
Supposons que vous appeliez setTimeout 4 fois pour créer 4 minuteries, avec des différences de 100, 200, 300 et 400 par rapport au temps t.

Explication détaillée du flux de travail et du cycle de vie de la boucle dévénements Node.js

Supposons que la boucle d'événements entre dans la phase de minuterie à t+250. Il examinera d'abord le temporisateur A, dont le délai d'expiration est t+100. Mais maintenant, il est temps t+250. Il exécutera donc le rappel lié au temporisateur A. Vérifiez ensuite le temporisateur B et constatez que son délai d'expiration est t+200, donc le rappel de B sera également exécuté. Maintenant, il va vérifier C et constater que son délai d'expiration est t+300, il le quittera donc. La boucle temporelle ne vérifie pas D car la minuterie est réglée par ordre croissant ; D a donc un seuil plus grand que C. Cependant, cette phase a une limite stricte dépendant du système, et si le nombre maximum de dépendances du système est atteint, elle passera à la phase suivante même s'il y a des minuteries non exécutées.

Workflow de la phase d'E/S en attente

Après la phase de minuterie, la boucle d'événements entrera dans la phase d'E/S en attente, puis vérifiera Vérifier s'il y a des rappels des tâches en attente précédentes dans la file d'attente_attente. Si tel est le cas, exécutez-les les uns après les autres jusqu'à ce que la file d'attente soit vide ou que la limite maximale du système soit atteinte. Après cela, la boucle d'événements passera à la phase de gestion inactive, suivie de la phase de préparation pour effectuer certaines opérations internes. Il se pourrait alors qu'elle entre enfin dans la phase la plus importante, celle du scrutin.

Flux de travail de la phase de sondage

Comme son nom l'indique, il s'agit d'une phase d'observation. Observez s’il y a de nouvelles demandes ou connexions entrantes. Lorsque la boucle d'événements entre dans la phase d'interrogation, elle exécutera des scripts dans watcher_queue, y compris les réponses de lecture de fichiers, les nouvelles demandes de socket ou de connexion http, jusqu'à ce que les événements soient épuisés ou que la limite de dépendance du système soit atteinte comme les autres phases. En supposant qu’il n’y ait aucun rappel à exécuter, le sondage attendra un certain temps sous certaines conditions. S'il y a des tâches en attente dans la file d'attente de vérification, la file d'attente en attente, la file d'attente des rappels de fermeture ou la file d'attente des gestionnaires inactifs, elle attendra 0 milliseconde. Il détermine ensuite le temps d'attente pour exécuter le premier temporisateur (si disponible) en fonction du tas du temporisateur. Si le premier seuil du timer est dépassé, il n'est pas nécessaire d'attendre (le premier timer sera exécuté).

Flux de travail de la phase de vérification

Une fois la phase d'interrogation terminée, la phase de vérification arrive immédiatement. Il y a des rappels déclenchés par l'API setImmediate dans la file d'attente à ce stade. Elle sera exécutée l'une après l'autre comme les autres étapes jusqu'à ce que la file d'attente soit vide ou que la limite maximale du système dépendant soit atteinte.

Workflow de rappel de fermeture

Après avoir terminé les tâches de la phase de vérification, la destination suivante de la boucle d'événements est de gérer le rappel de fermeture de type fermeture ou destruction. Une fois que la boucle d'événements a fini d'exécuter les rappels dans la file d'attente à ce stade, elle vérifie si la boucle est toujours active et, dans le cas contraire, se termine. Mais s’il y a encore du travail à faire, on passe à la boucle suivante ; d’où la phase timer. Si vous considérez que les minuteries (A et B) de l'exemple précédent ont expiré, la phase de minuterie démarrera désormais à partir de la minuterie C pour vérifier l'expiration.

nextTickQueue & microTaskQueue

Alors, quand les fonctions de rappel de ces deux files d'attente s'exécutent-elles ? Ils courent bien sûr le plus vite possible avant de passer de l'étape en cours à l'étape suivante. Contrairement aux autres étapes, ces deux étapes n'ont pas de restrictions de gueule de bois dépendantes du système et le nœud les exécute jusqu'à ce que les deux files d'attente soient vides. Cependant, nextTickQueue aura une priorité de tâche plus élevée que microTaskQueue.

Thread-pool

Un mot courant que j'ai entendu de la part des développeurs Javascript est ThreadPool. Une idée fausse courante est que nodejs dispose d'un pool de processus qui gère toutes les opérations asynchrones. Mais en fait, le pool de processus se trouve dans la bibliothèque libUV (une bibliothèque tierce utilisée par nodejs pour gérer les processus asynchrones). La raison pour laquelle il n’est pas représenté dans le diagramme est qu’il ne fait pas partie du mécanisme du cycle. Actuellement, toutes les tâches asynchrones ne seront pas traitées par le pool de processus. libUV utilise de manière flexible les API asynchrones du système d'exploitation pour que l'environnement reste piloté par les événements. Cependant, l'API du système d'exploitation ne peut pas effectuer de lecture de fichiers, de requête DNS, etc. Celles-ci sont gérées par le pool de processus, qui ne dispose que de 4 processus par défaut. Vous pouvez augmenter le nombre de processus en définissant la variable d'environnement de uv_threadpool_size jusqu'à 128.

Workflow avec exemples

J'espère que vous comprendrez comment fonctionne la boucle d'événements. Synchrone en langage C aide Javascript à devenir asynchrone. Il ne gère qu'une chose à la fois mais est très bloquant. Bien sûr, partout où nous décrivons la théorie, il est préférable de la comprendre avec des exemples. Comprenons donc ce script à travers quelques extraits de code.

Fragment 1 – Compréhension de base
setTimeout(() => {console.log('setTimeout'); }, 0); 
setImmediate(() => {console.log('setImmediate'); });

Pouvez-vous deviner le résultat ci-dessus ? Eh bien, vous pourriez penser que setTimeout serait imprimé en premier, mais il n'y a aucune garantie, pourquoi ? Après avoir exécuté le module principal et entré dans la phase de minuterie, il se peut qu'il ne constate pas que votre minuterie a expiré. Pourquoi? Un script de minuterie est enregistré en fonction de l'heure système et de l'heure delta que vous fournissez. En même temps que setTimeout est appelé, le script du minuteur est écrit dans la mémoire et il peut y avoir un léger délai en fonction des performances de votre machine et des autres opérations (pas des nœuds) en cours d'exécution sur celle-ci. À un autre moment, le nœud définit uniquement une variable maintenant avant d'entrer dans la phase de minuterie (chaque tour de parcours), en utilisant maintenant comme heure actuelle. On pourrait donc dire qu'il y a quelque chose qui ne va pas avec l'équivalent du temps précis. C'est la raison de l'incertitude. Si vous pointez sur le même code lors d'un rappel d'un code de minuterie, vous obtiendrez le même résultat.

Cependant, si vous déplacez ce code dans le cycle d'E/S, il est garanti que le rappel setImmediate s'exécutera avant setTimeout.

fs.readFile('my-file-path.txt', () => {
    setTimeout(() => {console.log('setTimeout');}, 0);               
    setImmediate(() => {console.log('setImmediate');}); });
Clip 2 — Meilleure compréhension des minuteries
var i = 0;
var start = new Date();
function foo () {
    i++;
    if (i <p>L'exemple ci-dessus est très simple. Appelez la fonction foo, puis appelez foo de manière récursive via setImmediate jusqu'à 1000. Sur mon ordinateur, cela a pris environ 6 à 8 millisecondes. Fée, veuillez modifier le code ci-dessus et remplacer setImmedaite(foo) par setTimeout(foo, o). </p><pre class="brush:php;toolbar:false">var i = 0;
var start = new Date();
function foo () {
    i++;
    if (i <p>À l'heure actuelle, l'exécution de ce code sur mon ordinateur prend plus de 1 400 ms. Pourquoi cela se produit-il ? Aucun d'entre eux n'a d'événements d'E/S, ils devraient être identiques. L'événement d'attente pour les deux exemples ci-dessus est 0. Pourquoi cela prend-il autant de temps ? Des écarts ont été constatés grâce à la comparaison d'événements, les tâches gourmandes en CPU prenant plus de temps. Les scripts de minuterie enregistrés consomment également des événements. Chaque étape d'un minuteur nécessite l'exécution de certaines opérations pour déterminer si un minuteur doit s'exécuter. Une exécution plus longue entraîne également plus de ticks. Cependant, dans setImmediate, il n'y a qu'une seule phase de vérification, comme si elle était dans une file d'attente puis exécutée. </p><h5><strong>Fragment 3 - Comprendre l'exécution de nextTick() et du timer (timer)</strong></h5><pre class="brush:php;toolbar:false">var i = 0;
function foo(){
    i++;
    if (i>20) return;
    console.log("foo");
    setTimeout(()=>console.log("setTimeout"), 0);       
    process.nextTick(foo);
}
setTimeout(foo, 2000);

Que pensez-vous du résultat ci-dessus ? Oui, il imprimera foo puis setTimeout. Après 2 secondes, foo() est appelé récursivement par nextTickQueue pour imprimer le premier foo. Lorsque tous les nextTickQueue sont exécutés, d'autres (tels que les rappels setTimeout) seront exécutés.

Donc, après l'exécution de chaque rappel, nextTickQueue commence à être vérifié ? Changeons le code et jetons un oeil.

var i = 0;
function foo(){
    i++;
    if (i>20) return;
    console.log("foo");
    setTimeout(()=>console.log("setTimeout"), 0);       
    process.nextTick(foo);
}
setTimeout(foo, 2000);
setTimeout(()=>{console.log("Other setTimeout"); }, 2000);

Après setTimeout, je viens d'ajouter un autre setTimeout qui génère un autre setTimeout avec le même temps de retard. Bien que cela ne soit pas garanti, il est possible que Other setTimeout soit affiché après la sortie du premier foo. Le même timer est regroupé dans un groupe et nextTickQueue sera exécuté après l'exécution du groupe de rappel en cours.

Quelques questions courantes

Où le code Javascript est-il exécuté ?

Comme la plupart d'entre nous pensent que la boucle d'événements se trouve dans un thread séparé, les rappels sont placés dans une file d'attente et exécutés les uns après les autres. Les lecteurs qui lisent cet article pour la première fois se demandent peut-être où Javascript est-il exécuté ? Comme je l'ai dit plus tôt, il n'y a qu'un seul thread, et le code Javascript de la boucle d'événements elle-même utilisant V8 ou d'autres moteurs est également exécuté ici. L'exécution est synchrone et la boucle d'événements ne se propagera pas si l'exécution Javascript en cours n'est pas terminée.

Nous avons setTimeout(fn, 0), pourquoi avons-nous besoin de setImmediate ?

Tout d'abord, ce n'est pas 0, mais 1. Lorsque vous réglez une minuterie avec un temps inférieur à 1, ou supérieur à 2147483647 ms, elle sera automatiquement réglée sur 1. Par conséquent, si vous définissez le temps de retard de setTimeout sur 0, il sera automatiquement défini sur 1.

De plus, setImmediate réduira les vérifications supplémentaires. Par conséquent, setImmediate s’exécutera plus rapidement. Il est également placé après la phase d'interrogation, de sorte que le rappel setImmediate de toute requête entrante sera exécuté immédiatement.

Pourquoi setImmediate est-il appelé par Comprehension ?

setImmediate et process.nextTick() sont tous deux mal nommés. Donc, fonctionnellement, setImmediate est exécuté au tick suivant et nextTick est exécuté immédiatement.

Le code Javascript sera-t-il bloqué ?

Parce que nextTickQueue n'a aucune limite sur l'exécution des rappels. Ainsi, si vous exécutez process.nextTick() de manière récursive, votre programme risque de ne jamais sortir de la boucle d'événements, peu importe ce que vous avez aux autres étapes.

Que se passe-t-il si j'appelle setTimeout pendant la phase de rappel de sortie ?

Il peut initialiser la minuterie, mais le rappel ne peut jamais être appelé. Parce que si le nœud est en phase de rappel de sortie, il est déjà sorti de la boucle d'événements. Il n’y a donc pas de retour en arrière pour exécuter.

Quelques brèves conclusions

La boucle d'événements n'a pas de pile de travail

La boucle d'événements n'est pas dans un thread séparé, et l'exécution de Javascript n'est pas comme celui de la file d'attente. C'est aussi simple que d'afficher un rappel à exécuter.

setImmediate ne pousse pas le rappel en tête de la file d'attente de travail, il existe une étape et une file d'attente dédiées.

setImmediate est exécuté dans la boucle suivante, et nextTick est en fait exécuté immédiatement.

Attention, nextTickQueue peut bloquer le code de votre nœud s'il est appelé de manière récursive.

Recommandations associées :

Analyse approfondie de l'événement Node.js loop_node.js

Introduction au cycle de vie de JS CONTROLS_Javascript Conseils

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