Existe-t-il en interne deux boucles d'événements dans l'architecture nodejs?
Sur une demande d'E/S, le nœud met-il en file d'attente la demande à libeio qui, à son tour, informe de la disponibilité des données via des événements utilisant libev et, enfin, ces événements sont gérés par la boucle d'événements v8 à l'aide de rappels?
En gros, comment libev et libeio sont-ils intégrés à l’architecture de nodejs?
Existe-t-il une documentation disponible pour donner une image claire de l'architecture interne de nodejs?
J'ai personnellement lu le code source de node.js & v8.
J'ai rencontré un problème similaire à vous, lorsque j'ai essayé de comprendre l'architecture de node.js afin d'écrire des modules natifs.
Ce que je publie ici, c’est ma compréhension de node.js et c’est peut-être un peu hors de propos.
Libev est la boucle d'événement qui s'exécute en interne dans node.js pour effectuer des opérations simples de boucle d'événement. Il a été écrit à l'origine pour les systèmes * nix. Libev fournit une boucle d'événements simple mais optimisée pour l'exécution du processus. Vous pouvez en savoir plus sur libev ici .
LibEio est une bibliothèque permettant d'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 à ce sujet ici ici .
LibUv est une couche d'abstraction située au sommet de libeio, libev, c-ares (pour DNS) et iocp (pour windows asynchronous-io). LibUv exécute, gère et gère tous les événements io et dans le pool d'événements. (en cas de libeio threadpool). Vous devriez consulter le tutoriel de Ryan Dahl sur libUv. Cela commencera à vous donner plus de sens sur la façon dont libUv fonctionne et vous comprendrez alors 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 .
En gros, dans node.js, 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 à thread unique)]. En dehors du thread principal, libev et libeio le gèrent dans le pool de threads et libev assure l'interaction avec la boucle principale. Donc, si j'ai bien compris, node.js a une boucle d'événements permanente: c'est la boucle d'événements v8. Pour gérer les tâches asynchrones C++, il utilise un pool de threads [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 Task
dans le pool de threads. Une fois terminé, il appelle la fonction AfterTask
dans le thread principal. Alors que Eio_REQUEST
est le gestionnaire de requêtes qui peut être une structure/objet dont le but est de fournir une communication entre le pool de threads et le thread principal.
On dirait que certaines des entités discutées (par exemple: libev, etc.) ont perdu de leur pertinence, en raison 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, à compter d'aujourd'hui.
Perspective du programme:
Le mécanisme d'événement ci-dessus est appelé cadre de boucle d'événement libuv AKA. Node exploite cette bibliothèque pour implémenter son modèle de programmation piloté par les événements.
Point de vue du nœud:
Alors que la plupart des fonctionnalités sont traitées 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 libuv. Tandis que les opérations d'E/S réseau peuvent attendre en attendant un événement externe tel que l'autre point de terminaison répondant aux données, etc., les opérations sur les fichiers nécessitent du travail du noeud 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 actuellement! En même temps, si vous lisez le fichier en ligne dans le thread principal, il peut potentiellement bloquer d'autres activités du programme et créer des problèmes évidents, car les opérations sur les fichiers sont très lentes par rapport aux activités liées à l'unité centrale. Ainsi, les threads de travail internes (configurables via la variable d'environnement UV_THREADPOOL_SIZE) sont utilisés pour opérer sur les fichiers, tandis que l'abstraction pilotée par les événements fonctionne de manière intacte, du point de vue du programme.
J'espère que cela t'aides.
Le projet node.js a débuté en 2009 sous la forme d'un environnement JavaScript découplé du navigateur. En utilisant V8 de Google et libev de Marc Lehmann, node.js a combiné un modèle d’E/S - evented - avec un langage bien adapté au style de programmation; en raison de la façon dont il a été façonné par les navigateurs. Au fur et à mesure que la popularité de node.js augmentait, il était important de le faire fonctionner sous Windows, mais libev ne fonctionnait que sous Unix. L'équivalent Windows des mécanismes de notification des é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 noeud-v0.9.0 de libuv libev a été supprimé .
Également une image décrivant la boucle d’événement dans Node.js par @ BusyRich
Mise à jour du 09/05/2017
Selon cette doc Node.js event loop ,
Le diagramme suivant montre une vue d'ensemble simplifiée de l'ordre des opérations de la boucle d'événement.
┌───────────────────────┐
┌─>│ timers │
│ └──────────┬────────────┘
│ ┌──────────┴────────────┐
│ │ I/O callbacks │
│ └──────────┬────────────┘
│ ┌──────────┴────────────┐
│ │ idle, prepare │
│ └──────────┬────────────┘ ┌───────────────┐
│ ┌──────────┴────────────┐ │ incoming: │
│ │ poll │<─────┤ connections, │
│ └──────────┬────────────┘ │ data, etc. │
│ ┌──────────┴────────────┐ └───────────────┘
│ │ check │
│ └──────────┬────────────┘
│ ┌──────────┴────────────┐
└──┤ close callbacks │
└───────────────────────┘
remarque: chaque case sera appelée "phase" de la boucle d'événement.
Aperçu des phases
setTimeout()
et setInterval()
.setImmediate()
. idle, prepare: utilisé uniquement en interne.setImmediate()
sont rappelés ici.socket.on('close', ...)
.Entre chaque exécution de la boucle d'événements, Node.js vérifie s'il attend des entrées/sorties asynchrones ou des timers et s'arrête proprement s'il n'y en a pas.
Il y a une boucle d'événement dans l'architecture NodeJs.
Les applications nodales s'exécutent dans un modèle mono-threadé piloté par les événements. 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 récupère l'élément en haut de la file d'attente, l'exécute, puis saisit l'élément suivant.
Lors de l'exécution de code ayant une durée de vie plus longue ou un blocage d'E/S, au lieu d'appeler directement la fonction, la fonction est ajoutée à la file d'attente des é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’attendre le blocage des E/S. Par conséquent, toutes les demandes qui exécutent des E/S bloquantes sont effectuées sur un autre thread en arrière-plan.
Lorsqu'un événement qui bloque les E/S est extrait de la file d'attente, Node.js récupère un thread du pool de threads et exécute la fonction à la place du thread de boucle d'événement principal. Cela empêche les E/S de blocage de bloquer le reste des événements dans la file d'attente des événements.
Il n'y a qu'une seule boucle d'événement fournie par libuv, V8 n'est qu'un moteur d'exécution JS.
La fonction pbkdf2
a l'implémentation JavaScript, mais elle délègue en réalité tout le travail à effectuer 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 concerne certaines fonctions très particulières de la bibliothèque standard.
Pour certains appels de fonction de bibliothèque standard, le côté Nœud C++ et Libuv décident de faire des calculs coûteux en dehors de la boucle d’événements.
Au lieu de cela, ils utilisent ce qu'on appelle un pool de threads, qui est une série de quatre threads pouvant être utilisés pour exécuter des tâches coûteuses en calcul, telles que la fonction pbkdf2
.
Par défaut, Libuv crée 4 threads dans ce pool de threads.
Outre les threads utilisés dans la boucle d'événement, quatre autres threads peuvent être utilisés pour décharger des calculs coûteux qui doivent être effectués dans notre application.
De nombreuses fonctions incluses dans la bibliothèque standard de nœuds utilisent automatiquement ce pool de threads. La fonction pbkdf2
en est une.
La présence de ce pool de threads est très significative.
Donc, Node n'est pas vraiment à un seul thread, car il utilise d'autres threads pour effectuer certaines tâches coûteuses en calcul.
Si le pool d'événements était responsable de l'exécution de la tâche coûteuse en calculs, notre application Nœud 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 effectuer d'autres opérations dans une boucle d'événements pendant les calculs.