Maison >interface Web >js tutoriel >Une brève discussion sur les minuteries dans Node.js_node.js
Implémentation du timer dans Node.js
Comme mentionné dans le billet de blog précédent, le timer dans Node n'est pas implémenté en ouvrant un nouveau thread, mais directement dans la boucle d'événements. Ce qui suit utilise plusieurs exemples de minuterie JavaScript et le code source associé à Node pour analyser la façon dont la fonction de minuterie est implémentée dans Node.
Caractéristiques de la fonction timer en JavaScript
Qu'il s'agisse de Node ou du navigateur, il existe deux fonctions de minuterie, setTimeout et setInterval, et leurs caractéristiques de fonctionnement sont fondamentalement les mêmes, donc ce qui suit utilise Node uniquement comme exemple d'analyse.
Nous savons que la minuterie en JavaScript n'est pas différente de l'interruption planifiée sous-jacente de l'ordinateur. Lorsqu'une interruption arrive, le code en cours d'exécution sera interrompu et transféré à la fonction de traitement d'interruption planifiée. Lorsque le délai JavaScript expire, si aucun code n'est en cours d'exécution dans le thread d'exécution en cours, la fonction de rappel correspondante sera exécutée ; s'il y a du code en cours d'exécution, le moteur JavaScript n'interrompra pas le code en cours pour exécuter le rappel, ni start Le nouveau thread exécute le rappel, mais il est traité après l'exécution du code actuel.
console.time('A') setTimeout(function () { console.timeEnd('A'); }, 100); var i = 0; for (; i < 100000; i++) { }
En exécutant le code ci-dessus, vous pouvez voir que le temps de sortie final n'est pas d'environ 100 ms, mais de quelques secondes. Cela montre que la fonction de rappel planifiée n'est effectivement pas exécutée avant la fin de la boucle, mais est reportée jusqu'à la fin de la boucle. En effet, lors de l'exécution du code JavaScript, tous les événements ne peuvent pas être traités, et de nouveaux événements doivent être traités jusqu'à ce que le code en cours soit terminé. C'est pourquoi le navigateur ne répond plus lors de l'exécution d'un code JavaScript fastidieux. Afin de faire face à cette situation, nous pouvons utiliser la technique Yielding Processes pour diviser le code fastidieux en petits morceaux (morceaux), exécuter setTimeout une fois après le traitement de chaque morceau et accepter de traiter le morceau suivant après une courte période de temps. Pendant cette période Pendant le temps d'inactivité, le navigateur/le nœud peut traiter les événements en file d'attente.
Informations complémentaires
Les minuteries avancées et les processus de rendement sont abordés plus en détail dans le chapitre 22 Techniques avancées de programmation avancée JavaScript, troisième édition.
Implémentation du minuteur dans Node
initialisation de libuv du type uv_loop_t
Le billet de blog précédent mentionnait que Node appellerait la fonction uv_run de libuv pour démarrer default_loop_ptr pour la planification d'événements. default_loop_ptr pointe vers une variable default_loop_struct de type uv_loop_t. Lorsque Node démarre, il appellera uv_loop_init(&default_loop_struct) pour l'initialiser. L'extrait de la fonction uv_loop_init est le suivant :
int uv_loop_init(uv_loop_t* loop) { ... loop->time = 0; uv_update_time(loop); ... }
Vous pouvez voir que le champ temporel de la boucle reçoit d'abord une valeur de 0, puis la fonction uv_update_time est appelée, qui attribue le dernier temps de comptage à loop.time.
Une fois l'initialisation terminée, default_loop_struct.time a une valeur initiale et les opérations liées au temps seront comparées à cette valeur pour déterminer s'il faut appeler la fonction de rappel correspondante.
Le noyau de planification d'événements de libuv
Comme mentionné précédemment, la fonction uv_run est la partie centrale de la bibliothèque libuv pour implémenter la boucle d'événements. Voici son organigramme :
Voici une brève description de la logique ci-dessus liée à la minuterie :
Mettre à jour le champ horaire de la boucle actuelle, qui marque le « maintenant » sous le concept de boucle actuelle
;
Vérifiez si la boucle est vivante, c'est-à-dire vérifiez s'il y a des tâches (gestionnaires/requêtes) qui doivent être traitées dans la boucle. Sinon, il n'est pas nécessaire de boucler
.
Vérifiez les minuteries enregistrées. Si l'heure spécifiée dans une minuterie est en retard sur l'heure actuelle, cela signifie que la minuterie a expiré et que sa fonction de rappel correspondante est exécutée
.
Effectuez une interrogation d'E/S (c'est-à-dire bloquez le thread et attendez que l'événement d'E/S se produise). Si aucune E/S n'est terminée à l'expiration du minuteur suivant, arrêtez d'attendre et exécutez le rappel du minuteur suivant.
Si un événement d'E/S se produit, le rappel correspondant sera exécuté ; puisqu'un autre temporisateur peut avoir expiré pendant le temps d'exécution du rappel, le temporisateur doit être à nouveau vérifié et le rappel exécuté.
(En fait (4.) ici, c'est plus compliqué, pas seulement une opération en une seule étape. Cette description vise simplement à ne pas impliquer d'autres détails et à se concentrer sur la mise en œuvre de la minuterie.)
Node continuera d'appeler uv_run jusqu'à ce que la boucle ne soit plus active.
timer_wrap et minuteries dans Node
Il existe une classe TimerWrap dans Node, qui est enregistrée en tant que module timer_wrap dans Node.
NODE_MODULE_CONTEXT_AWARE_BUILTIN(timer_wrap, node::TimerWrap::Initialize)
La classe TimerWrap est essentiellement une encapsulation directe de uv_timer_t, et NODE_MODULE_CONTEXT_AWARE_BUILTIN est une macro utilisée par Node pour enregistrer les modules intégrés.
Après cette étape, JavaScript peut récupérer ce module et le faire fonctionner. Le fichier src/lib/timers.js utilise JavaScript pour encapsuler la fonction timer_wrap et exporte exports.setTimeout, exports.setInterval, exports.setImmediate et d'autres fonctions.
Démarrage du nœud et initialisation globale
L'article précédent mentionnait que Node chargerait l'environnement d'exécution LoadEnvironment(env) au démarrage. Une étape très importante dans cette fonction consiste à charger src/node.js et à l'exécuter, src/node.js chargera le spécifié. module Et initialiser global et processus. Bien entendu, des fonctions telles que setTimeout seront également liées à l'objet global par src/node.js.
Ce qui précède représente l’intégralité du contenu de cet article, j’espère que vous l’aimerez tous.