Boucle d'événement Nodejs

141

Y a-t-il en interne deux boucles d'événements dans l'architecture nodejs?

  • libev / libuv
  • Boucle d'événements javascript v8

Sur une demande d'E / S, le nœud met-il en file d'attente la demande à libeio qui à son tour notifie la disponibilité des données via des événements à l'aide de libev et enfin, ces événements sont gérés par une boucle d'événements v8 à l'aide de rappels?

Fondamentalement, comment libev et libeio sont-ils intégrés dans l'architecture nodejs?

Existe-t-il une documentation disponible pour donner une image claire de l'architecture interne de nodejs?

Tamil
la source

Réponses:

175

J'ai personnellement lu le code source de node.js & v8.

Je suis entré dans un problème similaire comme vous lorsque j'ai essayé de comprendre l'architecture node.js afin d'écrire des modules natifs.

Ce que je publie ici, c'est ma compréhension de node.js et cela pourrait également être un peu décevant.

  1. Libev est la boucle d'événements qui s'exécute en fait en interne dans node.js pour effectuer des opérations simples de boucle d'événements. Il est écrit à l'origine pour les systèmes * nix. Libev fournit une boucle d'événements simple mais optimisée sur laquelle le processus peut s'exécuter. Vous pouvez en savoir plus sur libev ici .

  2. LibEio est une bibliothèque pour effectuer une sortie d'entrée de manière asynchrone. Il gère les descripteurs de fichiers, les gestionnaires de données, les sockets, etc. Vous pouvez en savoir plus ici ici .

  3. LibUv est une couche d'abstraction au-dessus de libeio, libev, c-ares (pour DNS) et iocp (pour windows asynchronous-io). LibUv exécute, maintient et gère tous les io et événements dans le pool d'événements. (dans le cas de libeio threadpool). Vous devriez consulter le tutoriel de Ryan Dahl sur libUv. Cela commencera à vous donner plus de sens sur le fonctionnement de libUv et vous comprendrez ensuite comment node.js fonctionne au-dessus de libuv et v8.

Pour comprendre uniquement la boucle d'événements javascript, vous devriez envisager de regarder ces vidéos

Pour voir comment libeio est utilisé avec node.js afin de créer des modules asynchrones, vous devriez voir cet exemple .

Fondamentalement, ce qui se passe à l'intérieur du node.js est que la boucle v8 s'exécute et gère toutes les parties javascript ainsi que les modules C ++ [lorsqu'ils s'exécutent dans un thread principal (selon la documentation officielle, node.js est lui-même à thread unique)]. En dehors du thread principal, libev et libeio le gèrent dans le pool de threads et libev fournissent l'interaction avec la boucle principale. Donc, d'après ce que je comprends, node.js a 1 boucle d'événements permanente: c'est la boucle d'événements v8. Pour gérer les tâches asynchrones C ++, il utilise un threadpool [via libeio & libev].

Par exemple:

eio_custom(Task,FLAG,AfterTask,Eio_REQUEST);

Ce qui apparaît dans tous les modules appelle généralement la fonction Taskdans le threadpool. Une fois terminé, il appelle la AfterTaskfonction dans le thread principal. Considérant que Eio_REQUESTc'est le gestionnaire de requêtes qui peut être une structure / un objet dont le motif est de fournir une communication entre le threadpool et le thread principal.

ShrekOverflow
la source
S'appuyer sur le fait que libuv utilise libev en interne est un bon moyen de rendre votre code non multiplateforme. Vous ne devriez vous soucier que de l'interface publique de libuv.
Raynos
1
@Raynos libuv vise à s'assurer que ses x-platfousing plusieurs bibliothèques. Droite ? donc utiliser libuv est une bonne idée
ShrekOverflow
1
@Abhishek From Doc process.nextTick- Lors de la prochaine boucle autour de la boucle d'événements, appelez ce rappel. Ce n'est pas un simple alias de setTimeout (fn, 0), c'est beaucoup plus efficace. À quelle boucle d'événements cela fait-il référence? Boucle d'événement V8?
Tamil le
2
Notez que libuv n'est plus implémenté au-dessus de libev .
strcat
4
Existe-t-il un moyen de «voir» cet événement que? J'aimerais pouvoir voir l'ordre des appels sur la pile et voir de nouvelles fonctions qui y sont poussées pour mieux comprendre ce qui se passe ... y a-t-il une variable qui vous indique ce qui a été poussé vers l'événement que?
tbarbe
20

On dirait que certaines des entités discutées (par exemple: libev etc.) ont perdu de leur pertinence, du fait que cela fait longtemps, mais je pense que la question a encore un grand potentiel.

Permettez-moi d'essayer d'expliquer le fonctionnement du modèle événementiel à l'aide d'un exemple abstrait, dans un environnement UNIX abstrait, dans le contexte de Node, à partir d'aujourd'hui.

Point de vue du programme:

  • Le moteur de script démarre l'exécution du script.
  • A chaque fois qu'une opération liée au CPU est rencontrée, elle est exécutée en ligne (machine réelle), dans son intégralité.
  • Chaque fois qu'une opération liée aux E / S est rencontrée, la requête et son gestionnaire d'achèvement sont enregistrés auprès d'une `` machine événementielle '' (machine virtuelle)
  • Répétez les opérations de la même manière ci-dessus jusqu'à la fin du script. Opération liée au processeur - exécutez celles liées aux E / S en ligne, demandez à la machine comme ci-dessus.
  • Lorsque les E / S sont terminées, les écouteurs sont rappelés.

La machinerie d'événement ci-dessus est appelée structure de boucle d'événement libuv AKA. Node exploite cette bibliothèque pour implémenter son modèle de programmation événementielle.

Point de vue du nœud:

  • Avoir un thread pour héberger le runtime.
  • Récupérez le script utilisateur.
  • Compilez-le en natif [leverage v8]
  • Chargez le binaire et sautez dans le point d'entrée.
  • Le code compilé exécute les activités liées au processeur en ligne, à l'aide de primitives de programmation.
  • De nombreux codes d'E / S et de minuterie ont des wraps natifs. Par exemple, E / S réseau.
  • Ainsi, les appels d'E / S sont acheminés du script vers les ponts C ++, avec le handle d'E / S et le gestionnaire de complétion passés comme arguments.
  • Le code natif exerce la boucle libuv. Il acquiert la boucle, met en file d'attente un événement de bas niveau qui représente les E / S et un wrapper de rappel natif dans la structure de la boucle libuv.
  • Le code natif revient au script - aucune E / S n'a lieu pour le moment!
  • Les éléments ci-dessus sont répétés plusieurs fois, jusqu'à ce que tout le code non-E / S soit exécuté et que tout le code d'E / S soit enregistré dans la libuv.
  • Enfin, lorsqu'il ne reste plus rien dans le système à exécuter, le nœud passe le contrôle à libuv
  • libuv entre en action, il récupère tous les événements enregistrés, interroge le système d'exploitation pour obtenir leur opérabilité.
  • Ceux qui sont prêts pour les E / S dans un mode non bloquant sont captés, les E / S sont exécutées et leurs rappels sont émis. L'un après l'autre.
  • Ceux qui ne sont pas encore prêts (par exemple une lecture de socket, pour laquelle l'autre point final n'a encore rien écrit) continueront à être sondés avec le système d'exploitation jusqu'à ce qu'ils soient disponibles.
  • La boucle maintient en interne une minuterie toujours croissante. Lorsque l'application demande un rappel différé (tel que setTimeout), cette valeur de minuterie interne est exploitée pour calculer le bon moment pour déclencher le rappel.

Alors que la plupart des fonctionnalités sont prises en charge de cette manière, certaines (versions asynchrones) des opérations sur les fichiers sont effectuées à l'aide de threads supplémentaires, bien intégrés dans la libuv. Alors que les opérations d'E / S réseau peuvent attendre dans l'attente d'un événement externe tel que l'autre point d'extrémité répondant avec des données, etc., les opérations sur les fichiers nécessitent un certain travail du nœud lui-même. Par exemple, si vous ouvrez un fichier et attendez que le fd soit prêt avec les données, cela ne se produira pas, car personne ne lit réellement! En même temps, si vous lisez à partir du fichier en ligne dans le thread principal, cela peut potentiellement bloquer d'autres activités dans le programme et créer des problèmes visibles, car les opérations sur les fichiers sont très lentes par rapport aux activités liées au processeur. Ainsi, des threads de travail internes (configurables via la variable d'environnement UV_THREADPOOL_SIZE) sont utilisés pour opérer sur les fichiers,

J'espère que cela t'aides.

Gireesh Punathil
la source
Comment saviez-vous ces choses, pouvez-vous m'indiquer la source?
Suraj Jain
18

Une introduction à libuv

Le projet node.js a débuté en 2009 sous la forme d'un environnement JavaScript découplé du navigateur. En utilisant le V8 de Google et la libev de Marc Lehmann , node.js a combiné un modèle d'E / S - événementiel - avec un langage bien adapté au style de programmation; en raison de la façon dont il a été façonné par les navigateurs. Alors que node.js gagnait en popularité, il était important de le faire fonctionner sous Windows, mais libev ne fonctionnait que sous Unix. L'équivalent Windows des mécanismes de notification d'événements du noyau tels que kqueue ou (e) poll est IOCP. libuv était une abstraction autour de libev ou IOCP selon la plate-forme, fournissant aux utilisateurs une API basée sur libev. Dans la version node-v0.9.0 de libuv, libev a été supprimée .

Aussi une image qui décrit la boucle d'événement dans Node.js par @ BusyRich


Mise à jour 05/09/2017

Selon cette boucle d'événements doc Node.js ,

Le diagramme suivant présente une vue d'ensemble simplifiée de l'ordre des opérations de la boucle d'événements.

   ┌───────────────────────┐
┌─>│        timers         
  └──────────┬────────────┘
  ┌──────────┴────────────┐
       I/O callbacks     
  └──────────┬────────────┘
  ┌──────────┴────────────┐
       idle, prepare     
  └──────────┬────────────┘      ┌───────────────┐
  ┌──────────┴────────────┐         incoming:   
           poll          │<─────┤  connections, 
  └──────────┬────────────┘         data, etc.  
  ┌──────────┴────────────┐      └───────────────┘
          check          
  └──────────┬────────────┘
  ┌──────────┴────────────┐
└──┤    close callbacks    
   └───────────────────────┘

note: chaque case sera appelée "phase" de la boucle d'événements.

Aperçu des phases

  • timers : cette phase exécute les callbacks programmés par setTimeout()et setInterval().
  • Callbacks d'E / S : exécute presque tous les callbacks à l'exception des rappels de fermeture , ceux programmés par les temporisateurs, et setImmediate().
  • idle, prepare : utilisé uniquement en interne.
  • poll : récupère les nouveaux événements d'E / S; le nœud se bloquera ici le cas échéant.
  • check : les setImmediate()callbacks sont appelés ici.
  • fermer les rappels : par exemple socket.on('close', ...).

Entre chaque exécution de la boucle d'événements, Node.js vérifie s'il attend des E / S asynchrones ou des minuteries et s'arrête proprement s'il n'y en a pas.

zangw
la source
Vous avez cité " In the node-v0.9.0 version of libuv libev was removed", mais il n'y a pas de description à ce sujet dans nodejs changelog. github.com/nodejs/node/blob/master/CHANGELOG.md . Et si libev est supprimé, comment les E / S asynchrones sont-elles effectuées dans nodejs?
intekhab
@intekhab, Par ce lien , je pense que la libuv basée sur libeio pourrait être utilisée comme boucle d'événement dans node.js.
zangw
@intekhab Je pense que libuv implémente toutes les fonctionnalités liées aux E / S et au sondage. ici, vérifiez ce document: docs.libuv.org/en/v1.x/loop.html
mohit kaushik le
13

Il existe une boucle d'événements dans l'architecture NodeJs.

Modèle de boucle d'événement Node.js

Les applications de nœud s'exécutent dans un modèle événementiel à thread unique. Cependant, Node implémente un pool de threads en arrière-plan afin que le travail puisse être effectué.

Node.js ajoute du travail à une file d'attente d'événements, puis un seul thread exécutant une boucle d'événements le récupère. La boucle d'événements saisit l'élément supérieur de la file d'attente d'événements, l'exécute, puis récupère l'élément suivant.

Lors de l'exécution d'un code dont la durée de vie est plus longue ou dont les E / S bloquent, au lieu d'appeler directement la fonction, il ajoute la fonction à la file d'attente d'événements avec un rappel qui sera exécuté une fois la fonction terminée. Lorsque tous les événements de la file d'attente d'événements Node.js ont été exécutés, l'application Node.js se termine.

La boucle d'événements commence à rencontrer des problèmes lorsque nos fonctions d'application se bloquent sur les E / S.

Node.js utilise des rappels d'événements pour éviter d'avoir à attendre le blocage des E / S. Par conséquent, toutes les demandes qui effectuent des E / S de blocage sont exécutées sur un thread différent en arrière-plan.

Lorsqu'un événement qui bloque les E / S est extrait de la file d'attente d'événements, Node.js récupère un thread du pool de threads et y exécute la fonction au lieu de sur le thread de la boucle d'événements principal. Cela empêche les E / S bloquantes de bloquer le reste des événements dans la file d'attente d'événements.

Peter Hauge
la source
8

Il n'y a qu'une seule boucle d'événements fournie par libuv, V8 n'est qu'un moteur d'exécution JS.

Warren Zhou
la source
1

En tant que débutant en javascript, j'avais également le même doute, NodeJS contient-il 2 boucles d'événements?. Après de longues recherches et discussions avec l'un des contributeurs de V8, j'ai obtenu les concepts suivants.

  • La boucle d'événements est un concept abstrait fondamental du modèle de programmation JavaScript. Ainsi, le moteur V8 fournit une implémentation par défaut pour la boucle d'événements, que les intégrateurs (navigateur, nœud) peuvent remplacer ou étendre . Vous pouvez trouver l'implémentation par défaut V8 de la boucle d'événements ici
  • Dans NodeJS, il n'existe qu'une seule boucle d'événements , qui est fournie par le moteur d'exécution du nœud. L'implémentation de la boucle d'événements par défaut V8 a été remplacée par l'implémentation de la boucle d'événements NodeJS
arunjos007
la source
0

La pbkdf2fonction a l'implémentation JavaScript mais elle délègue en fait tout le travail à faire du côté C ++.

env->SetMethod(target, "pbkdf2", PBKDF2);
  env->SetMethod(target, "generateKeyPairRSA", GenerateKeyPairRSA);
  env->SetMethod(target, "generateKeyPairDSA", GenerateKeyPairDSA);
  env->SetMethod(target, "generateKeyPairEC", GenerateKeyPairEC);
  NODE_DEFINE_CONSTANT(target, OPENSSL_EC_NAMED_CURVE);
  NODE_DEFINE_CONSTANT(target, OPENSSL_EC_EXPLICIT_CURVE);
  NODE_DEFINE_CONSTANT(target, kKeyEncodingPKCS1);
  NODE_DEFINE_CONSTANT(target, kKeyEncodingPKCS8);
  NODE_DEFINE_CONSTANT(target, kKeyEncodingSPKI);
  NODE_DEFINE_CONSTANT(target, kKeyEncodingSEC1);
  NODE_DEFINE_CONSTANT(target, kKeyFormatDER);
  NODE_DEFINE_CONSTANT(target, kKeyFormatPEM);
  NODE_DEFINE_CONSTANT(target, kKeyTypeSecret);
  NODE_DEFINE_CONSTANT(target, kKeyTypePublic);
  NODE_DEFINE_CONSTANT(target, kKeyTypePrivate);
  env->SetMethod(target, "randomBytes", RandomBytes);
  env->SetMethodNoSideEffect(target, "timingSafeEqual", TimingSafeEqual);
  env->SetMethodNoSideEffect(target, "getSSLCiphers", GetSSLCiphers);
  env->SetMethodNoSideEffect(target, "getCiphers", GetCiphers);
  env->SetMethodNoSideEffect(target, "getHashes", GetHashes);
  env->SetMethodNoSideEffect(target, "getCurves", GetCurves);
  env->SetMethod(target, "publicEncrypt",
                 PublicKeyCipher::Cipher<PublicKeyCipher::kPublic,
                                         EVP_PKEY_encrypt_init,
                                         EVP_PKEY_encrypt>);
  env->SetMethod(target, "privateDecrypt",
                 PublicKeyCipher::Cipher<PublicKeyCipher::kPrivate,
                                         EVP_PKEY_decrypt_init,
                                         EVP_PKEY_decrypt>);
  env->SetMethod(target, "privateEncrypt",
                 PublicKeyCipher::Cipher<PublicKeyCipher::kPrivate,
                                         EVP_PKEY_sign_init,
                                         EVP_PKEY_sign>);
  env->SetMethod(target, "publicDecrypt",
                 PublicKeyCipher::Cipher<PublicKeyCipher::kPublic,
                                         EVP_PKEY_verify_recover_init,
                                         EVP_PKEY_verify_recover>);

ressource: https://github.com/nodejs/node/blob/master/src/node_crypto.cc

Le module Libuv a une autre responsabilité qui est pertinente pour certaines fonctions très particulières de la bibliothèque standard.

Pour certains appels de fonction de bibliothèque standard, le côté Node C ++ et Libuv décident de faire des calculs coûteux en dehors de la boucle d'événements entièrement.

Au lieu de cela, ils utilisent quelque chose appelé pool de threads, le pool de threads est une série de quatre threads qui peuvent être utilisés pour exécuter des tâches coûteuses en calcul telles que la pbkdf2fonction.

Par défaut, Libuv crée 4 threads dans ce pool de threads.

En plus des threads utilisés dans la boucle d'événements, il existe quatre autres threads qui peuvent être utilisés pour décharger les calculs coûteux qui doivent se produire dans notre application.

De nombreuses fonctions incluses dans la bibliothèque standard Node utilisent automatiquement ce pool de threads. La pbkdf2fonction étant l'une d'entre elles.

La présence de ce pool de threads est très significative.

Node n'est donc pas vraiment à thread unique, car il existe d'autres threads que Node utilise pour effectuer des tâches coûteuses en calcul.

Si le pool d'événements était responsable de la tâche coûteuse en calcul, notre application Node ne pourrait rien faire d'autre.

Notre CPU exécute toutes les instructions dans un thread une par une.

En utilisant le pool de threads, nous pouvons faire d'autres choses dans une boucle d'événements pendant que les calculs sont en cours.

Daniel
la source