web-dev-qa-db-fra.com

Quand le pool de threads est-il utilisé?

Je comprends donc comment Node.js fonctionne: il n’a qu’un seul thread d’écoute qui reçoive un événement puis le délègue à un pool de travailleurs. Le thread de travail notifie l'écouteur une fois le travail terminé. L'écouteur renvoie ensuite la réponse à l'appelant.

Ma question est la suivante: si je mets en place un serveur HTTP dans Node.js et que je mets en veille un de mes événements de chemin routé (tel que "/ test/sleep"), tout le système s’arrête. Même le fil d'écoute unique. Mais si j'ai bien compris, ce code est présent dans le pool de travailleurs.

Maintenant, en revanche, lorsque j'utilise Mongoose pour parler à MongoDB, les lectures de base de données constituent une opération d'E/S coûteuse. Le nœud semble pouvoir déléguer le travail à un thread et recevoir le rappel une fois terminé. le temps de chargement à partir de la base de données ne semble pas bloquer le système.

Comment Node.js décide-t-il d'utiliser un thread de pool de threads par rapport au thread d'écoute? Pourquoi ne puis-je pas écrire un code d'événement qui dort et ne bloque qu'un thread de pool de threads?

81
Haney

Votre compréhension du fonctionnement d'un nœud n'est pas correcte ... mais c'est une idée fausse commune, car la réalité de la situation est en fait assez complexe et se résume généralement à de petites phrases comme "le nœud est à thread unique" qui simplifient excessivement les choses .

Pour le moment, nous allons ignorer les multi-traitements/multi-threads explicites via cluster et webworker-threads , et nous ne parlerons que de nœuds non-threadés typiques.

Le nœud s'exécute dans une boucle d'événement unique. C'est un seul thread, et vous ne recevez jamais qu'un seul thread. Tout le javascript que vous écrivez s'exécute dans cette boucle, et si une opération de blocage se produit dans ce code, elle bloquera toute la boucle et rien d'autre ne se passera jusqu'à la fin. C’est la nature typiquement simple du nœud dont vous entendez beaucoup parler. Mais ce n'est pas la situation dans son ensemble.

Certaines fonctions et certains modules, généralement écrits en C/C++, prennent en charge les E/S asynchrones. Lorsque vous appelez ces fonctions et méthodes, elles gèrent en interne le transfert de l'appel à un thread de travail. Par exemple, lorsque vous utilisez le module fs pour demander un fichier, le module fs transmet cet appel à un thread de travail et ce dernier attend sa réponse, qu'il présente ensuite à la boucle d'événement déclenchée sans ce dernier. pendant ce temps. Tout cela est éloigné de vous, développeur de nœud, et une partie est éloigné des développeurs de modules via l'utilisation de libuv .

Comme Denis Dollfus l'a souligné dans les commentaires (de cette réponse à une question similaire), la stratégie utilisée par libuv pour obtenir des E/S asynchrones n'est pas toujours un pool de threads, en particulier dans le cas du module http. stratégie différente semble être utilisé à ce moment. Pour nos besoins ici, il est principalement important de noter comment le contexte asynchrone est réalisé (en utilisant libuv) et que le pool de threads maintenu par libuv est l’une des multiples stratégies offertes par cette bibliothèque pour réaliser l’asynchronicité.


Sur une tangente généralement liée, il existe une analyse beaucoup plus approfondie de la manière dont les noeuds réalisent l'asynchronicité et de certains problèmes potentiels connexes et de la façon de les traiter, dans cet excellent article . La plus grande partie de ce que je viens d’écrire, mais cite aussi:

  • Tout module externe que vous incluez dans votre projet et qui utilise le C++ et libuv natifs est susceptible d’utiliser le pool de threads (pensez: accès à la base de données)
  • libuv a une taille de pool de threads par défaut de 4 et utilise une file d'attente pour gérer l'accès au pool de threads. En résumé, si vous avez 5 requêtes de base de données à exécution longue, toutes s'exécutant en même temps, l'une d'entre elles (et tout autre type asynchrone), action qui repose sur le pool de threads) attendra que ces requêtes soient terminées avant même de commencer
  • Vous pouvez atténuer ce problème en augmentant la taille du pool de threads via la variable d'environnement UV_THREADPOOL_SIZE, à condition de le faire avant que le pool de threads soit requis et créé: process.env.UV_THREADPOOL_SIZE = 10;

Si vous voulez un nœud traditionnel multi-traitement ou multi-thread, vous pouvez l'obtenir via le module intégré cluster ou divers autres modules tels que le webworker-threads susmentionné, ou vous pouvez le simuler en mettant en place un moyen de répartir votre travail manuellement. en utilisant setTimeout ou setImmediate ou process.nextTick pour suspendre votre travail et le continuer dans une boucle ultérieure pour laisser d'autres processus se terminer (mais ce n'est pas recommandé).

Veuillez noter que si vous écrivez du code en cours d'exécution/bloquant en javascript, vous faites probablement une erreur. D'autres langues fonctionneront beaucoup plus efficacement.

200
Jason

Je comprends donc comment Node.js fonctionne: il n’a qu’un seul thread d’écoute qui reçoive un événement puis le délègue à un pool de travailleurs. Le thread de travail notifie l'écouteur une fois le travail terminé. L'écouteur renvoie ensuite la réponse à l'appelant.

Ce n'est pas vraiment précis. Node.js a un seul thread "worker" qui exécute javascript. Il existe des threads dans le noeud qui gèrent le traitement IO, mais les considérer comme des "ouvriers" est une idée fausse. Il y a vraiment juste IO manipuler et quelques autres détails de l'implémentation interne de noeud, mais en tant que programmeur vous ne pouvez pas influencer leur comportement autre que quelques paramètres divers tels que MAX_LISTENERS.

Ma question est la suivante: si je mets en place un serveur HTTP dans Node.js et que je mets en veille un de mes événements de chemin routé (tel que "/ test/sleep"), tout le système s’arrête. Même le fil d'écoute unique. Mais si j'ai bien compris, ce code est présent dans le pool de travailleurs.

Il n'y a pas de mécanisme de sommeil en JavaScript. Nous pourrions en discuter plus concrètement si vous publiez un extrait de code de ce que vous pensez "dormir". Il n’existe aucune fonction de ce type à appeler pour simuler par exemple quelque chose comme time.sleep(30) en python. Il y a setTimeout mais c'est fondamentalement PAS dormir. setTimeout et setInterval explicitement release , not block, la boucle d'événements afin que d'autres bits de code puissent être exécutés sur le thread d'exécution principal. La seule chose que vous puissiez faire est d'utiliser le processeur en boucle avec le calcul en mémoire, ce qui privera le thread d'exécution principal et rendra votre programme inactif.

Comment Node.js décide-t-il d'utiliser un thread de pool de threads par rapport au thread d'écoute? Pourquoi ne puis-je pas écrire un code d'événement qui dort et ne bloque qu'un thread de pool de threads?

Le réseau IO est toujours asynchrone. Fin de l'histoire. Le disque IO possède à la fois des API synchrones et asynchrones. Il n'y a donc pas de "décision". node.js se comportera conformément aux fonctions de base de l'API que vous appelez sync vs async normal. Par exemple: fs.readFile vs fs.readFileSync. Pour les processus enfants, il existe également des API distinctes child_process.exec et child_process.execSync.

La règle empirique est toujours d'utiliser les API asynchrones. Les raisons valables pour utiliser les API de synchronisation concernent le code d'initialisation dans un service réseau avant l'écoute des connexions ou dans des scripts simples qui n'acceptent pas les requêtes réseau pour les outils de construction, etc.

17
Peter Lyons

Ce malentendu n’est que la différence entre le multitâche préemptif et le multitâche coopératif ... 

Le sommeil éteint tout le carnaval car il y a vraiment une ligne pour tous les manèges et vous avez fermé la porte. Pensez-y comme "un interprète de JS et quelques autres choses" et ignorez les threads ...

... alors ne le bloque pas.

0