Introduction
JavaScript est principalement exécuté sur un seul thread dans Node.js et dans le navigateur (à quelques exceptions près, telles que les threads de travail, ce qui sort du cadre de l'article actuel). Dans cet article, je vais essayer d'expliquer le mécanisme de concurrence chez Node.js qui est la boucle d'événement.
Avant de commencer à lire cet article, vous devez être familier avec la pile et son fonctionnement, j'ai déjà écrit sur cette idée, alors consultez Stack & Heap — Ne commencez pas à coder sans les comprendre — Moshe Binieli | Moyen
Image d'introduction
Exemples
Je pense que l'apprentissage est le meilleur par des exemples, je vais donc commencer par 4 exemples de code simples. J'analyserai les exemples puis je plongerai dans l'architecture de Node.js.
Exemple 1 :
console.log(1);
console.log(2);
console.log(3);
// Sortir:
// 1
// 2
// 3
Cet exemple est assez simple, à la première étape, le console.log(1) entre dans la pile d'appels, étant exécuté puis supprimé, à la deuxième étape, le console.log(2) entre dans la pile d'appels, en cours d'exécution puis supprimé, et ainsi de suite pour console.log(3).
Visualisation de la pile d'appels pour l'exemple 1
Exemple 2 :
console.log(1);
setTimeout(fonction foo(){
console.log(2);
}, 0);
console.log(3);
// Sortir:
// 1
// 3
// 2
Nous pouvons voir dans cet exemple que nous exécutons setTimeout immédiatement, nous nous attendrions donc à ce que console.log(2) soit avant console.log(3), mais ce n'est pas le cas et comprenons le mécanisme derrière cela.
Architecture de boucle d'événement de base (nous y reviendrons plus en détail plus tard)
Stack & Heap : consultez mon article sur celui-ci (j'ai ajouté un lien au début de cet article)
API Web : elles sont intégrées à votre navigateur Web et sont capables d'exposer les données du navigateur et de l'environnement informatique environnant et d'effectuer des tâches complexes et utiles avec celles-ci. Ils ne font pas partie du langage JavaScript lui-même, mais sont plutôt construits sur le langage JavaScript principal, vous offrant des super pouvoirs supplémentaires à utiliser dans votre code JavaScript. Par exemple, l'API de géolocalisation fournit des constructions JavaScript simples pour récupérer des données de localisation afin que vous puissiez tracer votre position sur une carte Google. En arrière-plan, le navigateur utilise en fait un code complexe de niveau inférieur (par exemple C ) pour communiquer avec le matériel GPS de l'appareil (ou tout ce qui est disponible pour déterminer les données de position), récupérer les données de position et les renvoyer à l'environnement du navigateur pour les utiliser. dans votre code. Mais encore une fois, cette complexité vous est éloignée par l'API.
Boucle d'événement et file d'attente de rappel : les fonctions qui ont terminé l'exécution de l'API Web sont déplacées vers la file d'attente de rappel, il s'agit d'une structure de données de file d'attente normale, et la boucle d'événement est responsable de retirer la fonction suivante de la file d'attente de rappel et d'envoyer la fonction à la pile d'appels pour exécuter la fonction.
Ordre d'exécution
Toutes les fonctions qui se trouvent actuellement dans la pile d'appels sont exécutées, puis retirées de la pile d'appels.
Lorsque la pile d'appels est vide, toutes les tâches en file d'attente sont placées une par une dans la pile d'appels et sont exécutées, puis elles sont retirées de la pile d'appels.
Comprenons l'exemple 2
console.log(1) est appelée et placée sur la pile d'appels et en cours d'exécution.
setTimeout est appelée et placée sur la pile d'appels et en cours d'exécution, cette exécution crée un nouvel appel à setTimeout Web Api pendant 0 milliseconde, lorsqu'elle se termine (immédiatement, ou pour être plus précis, elle le ferait Il serait préférable de dire « dès que possible » : l'API Web déplace l'appel vers la file d'attente de rappel.
console.log(3) est appelée et placée sur la pile d'appels et en cours d'exécution.
La boucle d'événements voit que la pile d'appels est vide et retire la méthode « foo » de la file d'attente de rappel et la place dans la pile d'appels, puis console.log(2) est en cours d'exécution.
Visualisation du processus pour l'exemple 2
Ainsi, le paramètre delay dans setTimeout(function, delay) ne représente pas le délai précis après lequel la fonction est exécutée. Il s'agit du temps d'attente minimum après lequel, à un moment donné, la fonction sera exécutée.
Exemple 3 :
console.log(1);
setTimeout(fonction foo() {
console.log('foo');
}, 3500);
setTimeout(fonction boo() {
console.log('boo');
}, 1000);
console.log(2);
// Sortir:
// 1
// 2
// 'huer'
// 'foo'
Visualisation du processus pour l'exemple 3
Exemple 4 :
console.log(1);
setTimeout(fonction foo() {
console.log('foo');
}, 6500);
setTimeout(fonction boo() {
console.log('boo');
}, 2500);
setTimeout(fonction baz() {
console.log('baz');
}, 0);
pour (valeur constante de ['A', 'B']) {
console.log(valeur);
}
fonction deux() {
console.log(2);
}
deux();
// Sortir:
// 1
// 'UN'
// 'B'
// 2
// 'baz'
// 'huer'
// 'foo'
Visualisation du processus pour l'exemple 4
La boucle d'événements procède à l'exécution de tous les rappels en attente dans la file d'attente des tâches. Dans la file d'attente des tâches, les tâches sont globalement classées en deux catégories, à savoir les micro-tâches et les macro-tâches.
Macro-tâches (file d'attente des tâches) et micro-tâches
Pour être plus précis, il existe en réalité deux types de files d'attente.
Il y a quelques tâches supplémentaires qui entrent dans la file d'attente des macro-tâches et de la file d'attente des micro-tâches, mais je couvrirai les plus courantes.
Les macro-tâches courantes sont setTimeout, setInterval et setImmediate.
Les micro-tâches courantes sont process.nextTick et Promise callback.
Ordre d'exécution
Toutes les fonctions qui se trouvent actuellement dans la pile d'appels sont exécutées, puis retirées de la pile d'appels.
Lorsque la pile d'appels est vide, toutes les micro-tâches en file d'attente sont placées une par une sur la pile d'appels et sont exécutées, puis elles sont retirées de la pile d'appels.
Lorsque la pile d'appels et la file d'attente des micro-tâches sont vides, toutes les macro-tâches en file d'attente sont placées une par une dans la pile d'appels et sont exécutées, puis elles sont retirées de la pile d'appels.
Exemple 5 :
console.log(1);
setTimeout(fonction foo() {
console.log('foo');
}, 0);
Promesse.resolve()
.then(fonction boo() {
console.log('boo');
});
console.log(2);
// Sortir:
// 1
// 2
// 'huer'
// 'foo'
La méthode console.log(1) est appelée et placée sur la pile d'appels et en cours d'exécution.
SetTimeout est en cours d'exécution, le console.log('foo') est déplacé vers SetTimeout Web Api, et 0 milliseconde plus tard, il est déplacé vers la file d'attente de macro-tâches.
Promise.resolve() est appelé, il est en cours de résolution, puis la méthode .then() est déplacée vers la file d'attente Micro-Task.
La méthode console.log(2) est appelée et placée sur la pile d'appels et en cours d'exécution.
Event Loop voit que la pile d'appels est vide, elle prend d'abord la tâche de la file d'attente Micro-Task qui est la tâche Promise, place le console.log('boo') sur la pile d'appels et l'exécute.
Event Loop voit que la pile d'appels est vide, puis elle voit que la micro-tâche est vide, puis elle prend la tâche suivante de la file d'attente des macro-tâches qui est la tâche SetTimeout, place le fichier console.log('foo') sur la pile d'appels et l'exécute.
Visualisation du processus pour l'exemple 5
Compréhension avancée de la boucle d'événement
Je pensais écrire sur le faible niveau de fonctionnement du mécanisme Event Loop, cela pourrait être un article en soi, j'ai donc décidé d'apporter une introduction au sujet et de joindre de bons liens qui expliquent le sujet en profondeur.
Niveau inférieur de la boucle d'événement expliqué
Lorsque Node.js démarre, il initialise la boucle d'événements, traite le script d'entrée fourni (ou le dépose dans le REPL) qui peut effectuer des appels d'API asynchrones, planifier des minuteries ou appeler process.nextTick(), puis commence à traiter la boucle d'événements. &&&]
Aperçu simplifié de l'ordre des opérations de la boucle d'événements
Chaque phase a une file d'attente FIFO de rappels à exécuter (je le dis avec précaution ici car il peut y avoir une autre structure de données en fonction de l'implémentation). Bien que chaque phase soit spéciale à sa manière, généralement, lorsque la boucle d'événements entre dans une phase donnée, elle effectuera toutes les opérations spécifiques à cette phase, puis exécutera des rappels dans la file d'attente de cette phase jusqu'à ce que la file d'attente soit épuisée ou que le nombre maximum de rappels soit atteint. a exécuté. Lorsque la file d'attente est épuisée ou que la limite de rappel est atteinte, la boucle d'événements passe à la phase suivante, et ainsi de suite.
Timers : cette phase exécute les rappels programmés par setTimeout() et setInterval().
Rappels en attente : exécute les rappels d'E/S différés à la prochaine itération de boucle.
Idle, Prepare : utilisé uniquement en interne.
Sondage : récupérer de nouveaux événements d'E/S ; exécuter les rappels liés aux E/S (presque tous à l'exception des rappels de fermeture, ceux programmés par les minuteries et setImmediate()) ; le nœud bloquera ici le cas échéant.
Vérifiez : les rappels setImmediate() sont invoqués ici.
Rappels de fermeture : certains rappels de fermeture, par ex. socket.on('fermer', ...).
Comment les étapes précédentes s’intègrent-elles ici ?
Ainsi, les étapes précédentes avec uniquement la « file d'attente de rappel », puis avec les « files d'attente macro et micro » étaient des explications abstraites sur le fonctionnement de la boucle d'événements.
Étape 1 : la boucle d'événements met à jour l'heure de la boucle à l'heure actuelle pour l'exécution en cours.
Étape 2 : La micro-file d'attente est exécutée.
Étape 3 : Une tâche de la phase Timers est exécutée.
Étape 4 : Vérifier s'il y a quelque chose dans la micro-file d'attente et exécuter l'intégralité de la micro-file d'attente s'il y a quelque chose.
Étape 5 : Revient à l'étape 3 jusqu'à ce que la phase Timers soit vide.
Étape 6 : Une tâche de la phase de rappels en attente est exécutée.
Étape 7 : Vérifier s'il y a quelque chose dans la micro-file d'attente et exécuter l'intégralité de la micro-file d'attente s'il y a quelque chose.
Étape 8 : retour à l'étape 6 jusqu'à ce que la phase de rappels en attente soit vide.
Et puis Idle… Micro-Queue… Sondage… Micro-Queue… Vérifiez… Micro-Queue… Fermez les rappels, puis tout recommence.
J'ai donc donné un bon aperçu de la façon dont la boucle d'événement fonctionne réellement dans les coulisses. Il y a beaucoup de parties manquantes que je n'ai pas mentionnées ici car la documentation actuelle fait un excellent travail en l'expliquant, je fournirai d'excellents liens pour la documentation, je vous encourage à investir 10 à 20 minutes et à les comprendre.
Clause de non-responsabilité: Toutes les ressources fournies proviennent en partie d'Internet. En cas de violation de vos droits d'auteur ou d'autres droits et intérêts, veuillez expliquer les raisons détaillées et fournir une preuve du droit d'auteur ou des droits et intérêts, puis l'envoyer à l'adresse e-mail : [email protected]. Nous nous en occuperons pour vous dans les plus brefs délais.
Copyright© 2022 湘ICP备2022001581号-3