Comprendre la boucle d'événements

135

J'y pense et c'est ce que j'ai trouvé:

Voyons ce code ci-dessous:

console.clear();
console.log("a");
setTimeout(function(){console.log("b");},1000);
console.log("c");
setTimeout(function(){console.log("d");},0);

Une demande arrive et le moteur JS commence à exécuter le code ci-dessus étape par étape. Les deux premiers appels sont des appels de synchronisation. Mais quand il s'agit de setTimeoutméthode, cela devient une exécution asynchrone. Mais JS en revient immédiatement et continue à s'exécuter, ce qui est appelé Non-Blockingou Async. Et il continue de travailler sur d'autres, etc.

Les résultats de cette exécution sont les suivants:

acdb

Donc, fondamentalement, le second setTimeoutest terminé en premier et sa fonction de rappel est exécutée plus tôt que la première et cela a du sens.

Nous parlons ici d'une application monothread. JS Engine continue d'exécuter cela et à moins qu'il ne termine la première requête, il ne passera pas à la seconde. Mais la bonne chose est qu'il n'attendra pas les opérations de blocage comme la setTimeoutrésolution, il sera donc plus rapide car il accepte les nouvelles demandes entrantes.

Mais mes questions se posent autour des éléments suivants:

# 1: Si nous parlons d'une application monothread, quel mécanisme traite setTimeoutsalors que le moteur JS accepte plus de requêtes et les exécute? Comment le thread unique continue-t-il de travailler sur d'autres demandes? Ce qui fonctionne pendant setTimeoutque d'autres demandes continuent d'arriver et sont exécutées.

# 2: Si ces setTimeoutfonctions sont exécutées dans les coulisses alors que d'autres requêtes arrivent et sont exécutées, qu'est-ce qui exécute les exécutions asynchrones dans les coulisses? Quelle est cette chose dont nous parlons appelé le EventLoop?

# 3: Mais la méthode entière ne devrait-elle pas être placée dans le EventLooppour que tout soit exécuté et que la méthode de rappel soit appelée? C'est ce que je comprends lorsque je parle des fonctions de rappel:

function downloadFile(filePath, callback)
{
   blah.downloadFile(filePath);
   callback();
}

Mais dans ce cas, comment le moteur JS sait-il s'il s'agit d'une fonction asynchrone afin de pouvoir placer le rappel dans le EventLoop? Peut-être quelque chose comme le asyncmot - clé en C # ou une sorte d'attribut qui indique la méthode que JS Engine prendra est une méthode asynchrone et devrait être traitée en conséquence.

# 4: Mais un article dit tout à fait contrairement à ce que je supposais sur la façon dont les choses pourraient fonctionner:

La boucle d'événements est une file d'attente de fonctions de rappel. Lorsqu'une fonction asynchrone s'exécute, la fonction de rappel est poussée dans la file d'attente. Le moteur JavaScript ne démarre pas le traitement de la boucle d'événements tant que le code après l'exécution d'une fonction asynchrone n'est pas exécuté.

# 5: Et il y a cette image ici qui pourrait être utile, mais la première explication de l'image dit exactement la même chose que celle mentionnée dans la question numéro 4:

entrez la description de l'image ici

Ma question ici est donc d'obtenir des éclaircissements sur les éléments énumérés ci-dessus?

Tarik
la source
1
Les threads ne sont pas la bonne métaphore pour gérer ces problèmes. Pensez aux événements.
Denys Séguret
1
@dystroy: Un exemple de code pour illustrer cette métaphore d'événement dans JS serait bien à voir.
Tarik le
Je ne vois pas quelle est exactement votre question ici.
Denys Séguret
1
@dystroy: Ma question ici est d'obtenir des précisions sur les éléments énumérés ci-dessus?
Tarik
2
Node n'est pas mono-thread mais cela n'a pas d'importance pour vous (mis à part le fait qu'il parvient à faire d'autres choses pendant que votre code utilisateur s'exécute). Un seul rappel au maximum dans votre code utilisateur est exécuté à la fois.
Denys Séguret

Réponses:

85

1: Si nous parlons d'une application à un seul thread, alors quels processus setTimeouts pendant que le moteur JS accepte plus de requêtes et les exécute? Ce thread unique ne continuera-t-il pas à travailler sur d'autres demandes? Alors qui va continuer à travailler sur setTimeout pendant que d'autres requêtes continuent d'arriver et sont exécutées.

Il n'y a qu'un seul thread dans le processus de nœud qui exécutera réellement le JavaScript de votre programme. Cependant, dans le nœud lui-même, il existe en fait plusieurs opérations de gestion de threads du mécanisme de boucle d'événements, et cela inclut un pool de threads IO et une poignée d'autres. La clé est que le nombre de ces threads ne correspond pas au nombre de connexions simultanées gérées comme ils le feraient dans un modèle de concurrence de thread par connexion.

Maintenant à propos de "l'exécution de setTimeouts", lorsque vous appelez setTimeout, tout ce que fait le nœud est essentiellement de mettre à jour une structure de données de fonctions à exécuter à un moment dans le futur. Il a essentiellement un tas de files d'attente de choses à faire et à chaque "tick" de la boucle d'événements, il en sélectionne un, le supprime de la file d'attente et l'exécute.

Une chose clé à comprendre est que le nœud repose sur le système d'exploitation pour la plupart des tâches lourdes. Ainsi, les demandes réseau entrantes sont en fait suivies par le système d'exploitation lui-même et lorsque le nœud est prêt à en gérer une, il utilise simplement un appel système pour demander au système d'exploitation une demande réseau avec des données prêtes à être traitées. Une grande partie du "travail" du nœud IO est soit "Hey OS, vous avez une connexion réseau avec des données prêtes à lire?" ou "Hey OS, l'un de mes appels de système de fichiers en suspens a des données prêtes?". Sur la base de son algorithme interne et de la conception de son moteur de boucle d'événements, le nœud sélectionnera un "tick" de JavaScript pour l'exécuter, l'exécuter, puis répéter le processus à nouveau. C'est ce que l'on entend par boucle d'événements. Node est fondamentalement en train de déterminer à tout moment "quel est le prochain petit morceau de JavaScript que je dois exécuter?", Puis de l'exécuter.setTimeoutou process.nextTick.

2: Si ces setTimeout sont exécutés dans les coulisses alors que plus de requêtes arrivent et sont en cours d'exécution, la chose qui effectue les exécutions asynchrones dans les coulisses est celle dont nous parlons EventLoop?

Aucun JavaScript n'est exécuté dans les coulisses. Tout le JavaScript de votre programme s'exécute au premier plan, un à la fois. Ce qui se passe dans les coulisses, c'est que le système d'exploitation gère les E / S et le nœud attend que cela soit prêt et le nœud gère sa file d'attente de javascript en attente d'exécution.

3: Comment JS Engine peut-il savoir s'il s'agit d'une fonction asynchrone afin de pouvoir la placer dans l'EventLoop?

Il existe un ensemble fixe de fonctions dans le nœud core qui sont asynchrones car elles effectuent des appels système et que le nœud sait lesquelles elles sont car elles doivent appeler le système d'exploitation ou C ++. Fondamentalement, toutes les E / S du réseau et du système de fichiers ainsi que les interactions de processus enfants seront asynchrones et la SEULE façon dont JavaScript peut amener le nœud à exécuter quelque chose de manière asynchrone est d'appeler l'une des fonctions asynchrones fournies par la bibliothèque principale de nœud. Même si vous utilisez un package npm qui définit sa propre API, afin de générer la boucle d'événements, le code de ce package npm appellera éventuellement l'une des fonctions asynchrones du noyau du nœud et c'est à ce moment que le nœud sait que la graduation est terminée et qu'il peut démarrer l'événement l'algorithme de boucle à nouveau.

4 La boucle d'événements est une file d'attente de fonctions de rappel. Lorsqu'une fonction asynchrone s'exécute, la fonction de rappel est poussée dans la file d'attente. Le moteur JavaScript ne démarre pas le traitement de la boucle d'événements tant que le code après l'exécution d'une fonction asynchrone n'est pas exécuté.

Oui, c'est vrai, mais c'est trompeur. L'essentiel est que le modèle normal soit:

//Let's say this code is running in tick 1
fs.readFile("/home/barney/colors.txt", function (error, data) {
  //The code inside this callback function will absolutely NOT run in tick 1
  //It will run in some tick >= 2
});
//This code will absolutely also run in tick 1
//HOWEVER, typically there's not much else to do here,
//so at some point soon after queueing up some async IO, this tick
//will have nothing useful to do so it will just end because the IO result
//is necessary before anything useful can be done

Donc oui, vous pouvez totalement bloquer la boucle d'événements en comptant simplement les nombres de Fibonacci de manière synchrone, tous en mémoire dans le même tick, et oui, cela gèlerait totalement votre programme. C'est la concurrence coopérative. Chaque tick de JavaScript doit générer la boucle d'événements dans un délai raisonnable, sinon l'architecture globale échoue.

Peter Lyons
la source
1
Disons que j'ai une file d'attente qui prendra 1 minute au serveur pour s'exécuter, et la première chose était une fonction asynchrone qui s'est terminée après 10 secondes. Va-t-il aller à la fin de la file d'attente ou va-t-il se pousser dans la ligne dès qu'il est prêt?
ilyo
4
En général, cela ira à la fin de la file d'attente, mais la sémantique de process.nextTickvs setTimeoutvs setImmediateest subtilement différente, même si vous ne devriez pas vraiment vous en soucier. J'ai un article de blog appelé setTimeout et des amis qui va plus en détail.
Peter Lyons
Pouvez-vous s'il vous plaît élaborer? Disons que j'ai deux rappels et le premier a une méthode changeColor avec un temps d'exécution de 10mS et un setTimeout de 1 minute et le second a une méthode changeBackground avec un temps d'exécution de 50ms avec un setTimeout de 10 secondes. Je sens que le changeBackground sera d'abord dans la file d'attente et que le changeColor sera le suivant. Après cela, la boucle Event sélectionne les méthodes de manière synchrone. Ai-je raison?
SheshPai
1
@SheshPai, c'est trop déroutant pour tout le monde de discuter du code lorsqu'il est écrit dans des paragraphes d'anglais. Postez simplement une nouvelle question avec un extrait de code afin que les gens puissent répondre en fonction du code au lieu d'une description du code, ce qui laisse beaucoup d'ambiguïté.
Peter Lyons
youtube.com/watch?v=QyUFheng6J0&spfreload=5 ceci est une autre bonne explication de JavaScript Engine
Mukesh Kumar
65

Il existe un didacticiel vidéo fantastique de Philip Roberts, qui explique la boucle d'événements javascript de la manière la plus simpliste et la plus conceptuelle. Chaque développeur javascript devrait y jeter un œil.

Voici le lien vidéo sur Youtube.

sam100rav
la source
16
Je l'ai regardé et c'était en effet la meilleure explication qui soit.
Tarik
1
Une vidéo incontournable pour les fans et les passionnés de JavaScript.
Nirus
1
cette vidéo change mon live ^^
HuyTran
1
est venu errer ici .. et c'est l'une des meilleures explications que j'ai eu .. merci pour le partage ...: D
RohitS
1
C'était une révélation
HebleV
11

Ne pensez pas que le processus hôte est monothread, ils ne le sont pas. Ce qui est mono-thread est la partie du processus hôte qui exécute votre code javascript.

Sauf pour les travailleurs de fond , mais ceux-ci compliquent le scénario ...

Ainsi, tout votre code js s'exécute dans le même thread, et il n'y a aucune possibilité que vous obteniez deux parties différentes de votre code js pour s'exécuter simultanément (vous n'obtenez donc pas de problème de concurrence à gérer).

Le code js en cours d'exécution est le dernier code que le processus hôte a récupéré dans la boucle d'événements. Dans votre code, vous pouvez essentiellement faire deux choses: exécuter des instructions synchrones et planifier des fonctions à exécuter à l'avenir, lorsque certains événements se produisent.

Voici ma représentation mentale (attention: c'est juste ça, je ne connais pas les détails d'implémentation du navigateur!) De votre exemple de code:

console.clear();                                   //exec sync
console.log("a");                                  //exec sync
setTimeout(                //schedule inAWhile to be executed at now +1 s 
    function inAWhile(){
        console.log("b");
    },1000);    
console.log("c");                                  //exec sync
setTimeout(
    function justNow(){          //schedule justNow to be executed just now
        console.log("d");
},0);       

Pendant que votre code est en cours d'exécution, un autre thread du processus hôte effectue le suivi de tous les événements système qui se produisent (clics sur l'interface utilisateur, fichiers lus, paquets réseaux reçus, etc.)

Une fois votre code terminé, il est supprimé de la boucle d'événements et le processus hôte revient à le vérifier pour voir s'il reste du code à exécuter. La boucle d'événements contient deux gestionnaires d'événements de plus: un à exécuter maintenant (la fonction justNow) et un autre en une seconde (la fonction inAWhile).

Le processus hôte essaie maintenant de faire correspondre tous les événements qui se sont produits pour voir si des gestionnaires sont enregistrés pour eux. Il a constaté que l'événement attendu par justNow s'est produit, il a donc commencé à exécuter son code. Lorsque la fonction justNow se termine, elle vérifie la boucle d'événements une autre fois, recherchant des gestionnaires d'événements. En supposant que 1 s s'est écoulée, il exécute la fonction inAWhile, et ainsi de suite ...

Andrea Parodi
la source
Le setTimeout est cependant implémenté dans le thread principal. Il n'y a donc rien dans votre exemple qui nécessite un thread séparé. En fait, dans les navigateurs, seuls les onglets sont implémentés dans plusieurs threads. Dans un seul onglet, tous les processus, y compris la création de plusieurs connexions réseau parallèles, l'attente de clics de souris, setTimeout, animations, etc. sont effectués dans le même thread
slebetman il y a