Maison  >  Article  >  interface Web  >  Explication détaillée de la boucle d'événements Node.js (Event Loop) et du thread pool_node.js

Explication détaillée de la boucle d'événements Node.js (Event Loop) et du thread pool_node.js

WBOY
WBOYoriginal
2016-05-16 16:17:231218parcourir

La « boucle d'événement » de Node est au cœur de sa capacité à gérer une grande concurrence et un débit élevé. C'est la partie la plus magique, selon laquelle Node.js peut fondamentalement être compris comme "mono-thread", tout en permettant également de traiter des opérations arbitraires en arrière-plan. Cet article clarifiera le fonctionnement de la boucle d'événements afin que vous puissiez ressentir sa magie.

Programmation événementielle

Pour comprendre la boucle d'événements, vous devez d'abord comprendre la programmation pilotée par les événements. Il est apparu en 1960. De nos jours, la programmation événementielle est largement utilisée dans la programmation de l’interface utilisateur. L'une des principales utilisations de JavaScript est d'interagir avec le DOM, il est donc naturel d'utiliser une API basée sur les événements.

Défini simplement : la programmation basée sur les événements contrôle le flux d'une application à travers des événements ou des changements d'état. Généralement mis en œuvre via la surveillance d'événements, une fois l'événement détecté (c'est-à-dire que l'état change), la fonction de rappel correspondante est appelée. Cela vous semble familier ? En fait, c'est le principe de fonctionnement de base de la boucle d'événements Node.js.

Si vous êtes familier avec le développement JavaScript côté client, pensez aux méthodes .on*(), telles que element.onclick(), qui sont utilisées pour se combiner avec des éléments DOM pour offrir une interaction utilisateur. Ce mode de fonctionnement permet de déclencher plusieurs événements sur une seule instance. Node.js déclenche ce modèle via des EventEmitters (générateurs d'événements), comme dans les modules Socket et "http" côté serveur. Un ou plusieurs changements d'état peuvent être déclenchés à partir d'une seule instance.

Un autre schéma courant consiste à exprimer le succès et l’échec. Il existe généralement deux méthodes de mise en œuvre courantes. La première consiste à transmettre « l'exception d'erreur » dans le rappel, généralement comme premier paramètre de la fonction de rappel. Le second utilise le modèle de conception Promises et a ajouté ES6. Remarque* Le mode Promesse utilise une méthode d'écriture de chaîne de fonctions similaire à jQuery pour éviter une imbrication profonde des fonctions de rappel, telles que :

Copier le code Le code est le suivant :

$.getJSON('/getUser').done(successHandler).fail(failHandler)

Le module "fs" (système de fichiers) adopte principalement le style de transmission des exceptions dans les rappels. Déclenchant techniquement certains appels, comme l'événement attaché fs.readFile(), mais l'API sert simplement à alerter l'utilisateur et à exprimer le succès ou l'échec de l'opération. Le choix d'une telle API repose sur des considérations architecturales plutôt que sur des limitations techniques.

Une idée fausse courante est que les émetteurs d'événements sont également intrinsèquement asynchrones lors du déclenchement d'événements, mais c'est incorrect. Vous trouverez ci-dessous un simple extrait de code pour le démontrer.

Copier le code Le code est le suivant :

fonction MonEmitter() {
EventEmitter.call(this);
>
util.inherits(MyEmitter, EventEmitter);

MyEmitter.prototype.doStuff = function doStuff() {
console.log('avant')
émetteur.emit('feu')
console.log('after')}
};

var me = new MyEmitter();
moi.on('fire', function() {
console.log('emit Fire');
});

me.doStuff();
// Sortie :
// avant
// émet tiré
// après

Remarque* Si émetteur.emit est asynchrone, la sortie doit être
// avant
// après
// émet tiré


EventEmitter se comporte souvent de manière asynchrone car il est souvent utilisé pour notifier les opérations qui doivent être effectuées de manière asynchrone, mais l'API EventEmitter elle-même est entièrement synchrone. Les fonctions d'écoute peuvent être exécutées de manière asynchrone en interne, mais veuillez noter que toutes les fonctions d'écoute seront exécutées de manière synchrone dans l'ordre dans lequel elles sont ajoutées.

Aperçu du mécanisme et pool de threads

Node lui-même repose sur plusieurs bibliothèques. L'un d'eux est libuv, l'étonnante bibliothèque permettant de gérer les files d'attente et l'exécution d'événements asynchrones.

Node utilise autant que possible le noyau du système d'exploitation pour implémenter les fonctions existantes. Comme générer des demandes de réponse, transférer les connexions et les confier au système pour traitement. Par exemple, les connexions entrantes sont mises en file d'attente via le système d'exploitation jusqu'à ce qu'elles puissent être gérées par Node.

Vous avez peut-être entendu dire que Node dispose d'un pool de threads, et vous vous demandez peut-être : "Si Node traite les tâches dans l'ordre, pourquoi avons-nous besoin d'un pool de threads ?" commande exécutée de manière asynchrone. Dans ce cas, Node.JS doit être capable de verrouiller le thread pendant un certain temps pendant son fonctionnement afin qu'il puisse continuer à exécuter la boucle d'événements sans être bloqué.

Ce qui suit est un exemple de schéma simple pour montrer son mécanisme de fonctionnement interne :


┌──────────────────────┐
╭──►│ minuteries minuteries │        └───────────┬───────────┘
│       ┌───────────┴───────────┐
│                                                                                                                                                                                                                                   rappels en attente                                           │          └──────────┬───────────┘                                                                                                                                                                         | │ │ │ SONDAGE ││── Connexions, │
│                                                                                                                                                                                                                                                        depuis │           ┌───────────┴───────────┐                                                                                                                                              ╰─── ┤ setImmédiat └───────────────────────┘

Il y a certaines choses difficiles à comprendre concernant le fonctionnement interne de la boucle événementielle :

Tous les rappels seront prédéfinis via process.nextTick() à la fin d'une étape de la boucle d'événement (par exemple, une minuterie) et avant de passer à l'étape suivante. Cela évitera les appels récursifs potentiels à process.nextTick(), provoquant une boucle infinie.
Les « rappels en attente » sont des rappels dans la file d'attente de rappel qui ne seront traités par aucun autre cycle de boucle d'événement (par exemple, transmis à fs.write).

Émetteur d'événement et boucle d'événement

Simplifiez l'interaction avec la boucle d'événements en créant un EventEmitter. Il s'agit d'un wrapper générique qui vous permet de créer plus facilement des API basées sur des événements. La façon dont les deux interagissent laisse souvent les développeurs confus.

L'exemple suivant montre qu'oublier qu'un événement est déclenché de manière synchrone peut entraîner la perte de l'événement.

Copier le code Le code est le suivant :

// Après la v0.10, require('events').EventEmitter n'est plus nécessaire
var EventEmitter = require('events');
var util = require('util');

fonction MyThing() {
EventEmitter.call(this);

doFirstThing();
this.emit('thing1');
>
util.inherits(MyThing, EventEmitter);

var mt = new MyThing();

mt.on('thing1', function onThing1() {
// Désolé, cet événement n'arrivera jamais
});


L'événement 'thing1' ci-dessus ne sera jamais capturé par MyThing() car MyThing() doit être instancié avant de pouvoir écouter les événements. Voici une solution de contournement simple sans ajouter de fermetures supplémentaires :
Copier le code Le code est le suivant :

var EventEmitter = require('events');
var util = require('util');

fonction MyThing() {
EventEmitter.call(this);

doFirstThing();
setImmediate(emitThing1, this);
>
util.inherits(MyThing, EventEmitter);

fonction émetThing1(self) {
self.emit('thing1');
>

var mt = new MyThing();

mt.on('thing1', function onThing1() {
// Exécuté
});

La solution suivante fonctionnera également, mais au détriment de certaines performances :

Copier le code Le code est le suivant :

fonction MyThing() {
EventEmitter.call(this);

doFirstThing();
// L'utilisation de Function#bind() perdra en performances
setImmediate(this.emit.bind(this, 'thing1'));
>
util.inherits(MyThing, EventEmitter);


Un autre problème est le déclenchement d’une erreur. Trouver des problèmes dans votre application est déjà assez difficile, mais sans la pile d'appels (note *e.stack), le débogage est presque impossible. Lorsqu'une erreur est reçue par une requête asynchrone distante, la pile d'appels sera perdue. Il existe deux solutions possibles : déclencher de manière synchrone ou garantir que l'erreur est transmise avec d'autres informations importantes. Les exemples ci-dessous illustrent les deux solutions :
Copier le code Le code est le suivant :

MyThing.prototype.foo = function foo() {
// Cette erreur sera déclenchée de manière asynchrone
var euh = doFirstThing();
si (euh) {
//Lorsqu'elle est déclenchée, une nouvelle erreur doit être créée pour conserver les informations de la pile d'appels sur site
setImmediate(emitError, this, new Error('Bad stuff'));
Retour ;
>

// Déclenchez une erreur et gérez-la immédiatement (de manière synchrone)
var euh = doSecondThing();
si (euh) {
This.emit('erreur', 'Plus de mauvaises choses');
Retour ;
>
>


Évaluez la situation. Lorsqu'une erreur est déclenchée, elle peut être traitée immédiatement. Ou bien, il peut s’agir d’une exception triviale qui peut être facilement gérée ou traitée plus tard. De plus, transmettre Error via un constructeur n'est pas une bonne idée, car l'instance d'objet construite est probablement incomplète. Le cas où Error a été lancé directement tout à l'heure est une exception.

Conclusion

Cet article aborde brièvement le fonctionnement interne et les détails techniques de la boucle d'événements. Tout est bien pensé. Un autre article discutera de l'interaction de la boucle d'événements avec le noyau du système et montrera la magie du fonctionnement asynchrone de NodeJS.

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