Node a un paradigme complètement différent et une fois qu'il est correctement capturé, il est plus facile de voir cette manière différente de résoudre les problèmes. Vous n'avez jamais besoin de plusieurs threads dans une application Node (1) car vous avez une manière différente de faire la même chose. Vous créez plusieurs processus; mais c'est très très différent, par exemple, de la façon dont Prefork mpm d'Apache Web Server le fait.
Pour l'instant, pensons que nous n'avons qu'un seul cœur de processeur et nous allons développer une application (à la manière de Node) pour faire un peu de travail. Notre travail consiste à traiter un gros fichier en cours d'exécution sur son contenu octet par octet. La meilleure façon pour notre logiciel est de commencer le travail depuis le début du fichier, de le suivre octet par octet jusqu'à la fin.
- Hé, Hasan, je suppose que tu es soit un débutant, soit une très vieille école du temps de mon grand-père !!! Pourquoi ne pas créer des threads et le rendre beaucoup plus rapide?
- Oh, nous n'avons qu'un seul cœur de processeur.
-- Et alors? Créez des fils, faites-le plus vite!
-- Ça ne marche pas comme ça. Si je crée des fils, je vais le ralentir. Parce que j'ajouterai beaucoup de frais généraux au système pour basculer entre les threads, en essayant de leur donner un peu de temps, et à l'intérieur de mon processus, en essayant de communiquer entre ces threads. En plus de tous ces faits, je devrai également réfléchir à la façon dont je vais diviser un même travail en plusieurs morceaux qui peuvent être réalisés en parallèle.
- D'accord, je vois que tu es pauvre. Utilisons mon ordinateur, il a 32 cœurs!
- Wow, tu es génial mon cher ami, merci beaucoup. Je vous en suis reconnaissant!
Ensuite, nous retournons au travail. Nous avons maintenant 32 cœurs cpu grâce à notre riche ami. Les règles que nous devons respecter viennent de changer. Maintenant, nous voulons utiliser toute cette richesse qui nous est donnée.
Pour utiliser plusieurs cœurs, nous devons trouver un moyen de diviser notre travail en morceaux que nous pouvons gérer en parallèle. Si ce n'était pas Node, nous utiliserions des threads pour cela; 32 threads, un pour chaque cœur de processeur. Cependant, puisque nous avons Node, nous allons créer 32 processus Node.
Les threads peuvent être une bonne alternative aux processus Node, peut-être même un meilleur moyen; mais seulement dans un type de travail spécifique où le travail est déjà défini et nous avons un contrôle total sur la façon de le gérer. En dehors de cela, pour tout autre type de problème où le travail vient de l'extérieur d'une manière que nous n'avons pas de contrôle et que nous voulons répondre le plus rapidement possible, la manière de Node est incontestablement supérieure.
- Hé, Hasan, tu travailles toujours avec un seul thread? Qu'est-ce qui ne va pas avec toi, mec? Je viens de vous fournir ce que vous vouliez. Vous n'avez plus d'excuses. Créez des threads, accélérez-le.
- J'ai divisé le travail en morceaux et chaque processus fonctionnera sur l'une de ces pièces en parallèle.
- Pourquoi ne créez-vous pas de fils?
- Désolé, je ne pense pas que ce soit utilisable. Vous pouvez emporter votre ordinateur si vous le souhaitez?
- Non d'accord, je suis cool, je ne comprends juste pas pourquoi vous n'utilisez pas de fils?
- Merci pour l'ordinateur. :) J'ai déjà divisé le travail en morceaux et je crée des processus pour travailler sur ces morceaux en parallèle. Tous les cœurs du processeur seront pleinement utilisés. Je pourrais le faire avec des threads au lieu de processus; mais Node a cette façon et mon patron Parth Thakkar veut que j'utilise Node.
- D'accord, faites-moi savoir si vous avez besoin d'un autre ordinateur. : p
Si je crée 33 processus, au lieu de 32, le planificateur du système d'exploitation mettra en pause un thread, démarrera l'autre, le mettra en pause après quelques cycles, redémarrera l'autre ... C'est une surcharge inutile. Je ne le veux pas. En fait, sur un système avec 32 cœurs, je ne voudrais même pas créer exactement 32 processus, 31 peuvent être plus agréables . Parce que ce n'est pas seulement mon application qui fonctionnera sur ce système. Laisser un peu de place pour d'autres choses peut être bien, surtout si nous avons 32 chambres.
Je pense que nous sommes sur la même longueur d'onde maintenant sur l'utilisation complète des processeurs pour les tâches gourmandes en CPU .
- Hmm, Hasan, je suis désolé de me moquer un peu de toi. Je crois que je te comprends mieux maintenant. Mais il y a encore quelque chose dont j'ai besoin d'une explication: qu'est-ce que tout le buzz autour de l'exécution de centaines de threads? Je lis partout que les threads sont beaucoup plus rapides à créer et stupides que les processus de fourche? Vous forkez des processus au lieu de threads et vous pensez que c'est le plus élevé que vous obtiendriez avec Node. Alors Node n'est-il pas approprié pour ce genre de travail?
- Pas de soucis, je suis cool aussi. Tout le monde dit ces choses, alors je pense que j'ai l'habitude de les entendre.
-- Alors? Node n'est pas bon pour ça?
- Node est parfaitement adapté à cela même si les threads peuvent l'être aussi. Quant à la surcharge de création de thread / processus; sur des choses que vous répétez beaucoup, chaque milliseconde compte. Cependant, je ne crée que 32 processus et cela prendra très peu de temps. Cela n'arrivera qu'une seule fois. Cela ne fera aucune différence.
- Quand est-ce que je veux créer des milliers de fils, alors?
- Vous ne voulez jamais créer des milliers de fils. Cependant, sur un système qui effectue un travail qui vient de l'extérieur, comme un serveur Web traitant des requêtes HTTP; si vous utilisez un thread pour chaque requête, vous allez créer beaucoup de threads, dont beaucoup.
- Node est différent, cependant? Droite?
-- Oui, exactement. C'est là que Node brille vraiment. Tout comme un thread est beaucoup plus léger qu'un processus, un appel de fonction est beaucoup plus léger qu'un thread. Le nœud appelle des fonctions au lieu de créer des threads. Dans l'exemple d'un serveur Web, chaque requête entrante provoque un appel de fonction.
-- Hum ... intéressant; mais vous ne pouvez exécuter qu'une seule fonction à la fois si vous n'utilisez pas plusieurs threads. Comment cela peut-il fonctionner lorsque de nombreuses requêtes arrivent sur le serveur Web en même temps?
- Vous avez parfaitement raison sur la manière dont les fonctions fonctionnent, une à la fois, jamais deux en parallèle. Je veux dire dans un seul processus, une seule portée de code est en cours d'exécution à la fois. L'OS Scheduler ne vient pas mettre en pause cette fonction et passer à une autre, à moins qu'il n'interrompe le processus pour donner du temps à un autre processus, pas à un autre thread de notre processus. (2)
- Alors, comment un processus peut-il gérer 2 demandes à la fois?
- Un processus peut traiter des dizaines de milliers de requêtes à la fois tant que notre système dispose de suffisamment de ressources (RAM, réseau, etc.). Le fonctionnement de ces fonctions est LA DIFFÉRENCE CLÉ.
- Hmm, devrais-je être excité maintenant?
- Peut-être :) Node exécute une boucle sur une file d'attente. Dans cette file d'attente se trouvent nos travaux, c'est-à-dire les appels que nous avons commencé à traiter les demandes entrantes. Le point le plus important ici est la façon dont nous concevons nos fonctions pour qu'elles s'exécutent. Au lieu de commencer à traiter une demande et de faire attendre l'appelant jusqu'à ce que nous ayons terminé le travail, nous terminons rapidement notre fonction après avoir effectué une quantité de travail acceptable. Lorsque nous arrivons à un point où nous devons attendre qu'un autre composant fasse du travail et nous renvoie une valeur, au lieu d'attendre cela, nous finissons simplement notre fonction en ajoutant le reste du travail à la file d'attente.
- Cela semble trop complexe?
- Non non, je peux paraître complexe; mais le système lui-même est très simple et il est parfaitement logique.
Maintenant, je veux arrêter de citer le dialogue entre ces deux développeurs et terminer ma réponse après un dernier exemple rapide du fonctionnement de ces fonctions.
De cette façon, nous faisons ce que ferait normalement OS Scheduler. Nous suspendons notre travail à un moment donné et laissons d'autres appels de fonction (comme d'autres threads dans un environnement multi-thread) s'exécuter jusqu'à ce que nous ayons à nouveau notre tour. C'est bien mieux que de laisser le travail à OS Scheduler qui essaie de donner juste du temps à chaque thread sur le système. Nous savons ce que nous faisons beaucoup mieux que OS Scheduler et nous devons nous arrêter quand nous devrions arrêter.
Voici un exemple simple où nous ouvrons un fichier et le lisons pour travailler sur les données.
Voie synchrone:
Open File
Repeat This:
Read Some
Do the work
Manière asynchrone:
Open File and Do this when it is ready: // Our function returns
Repeat this:
Read Some and when it is ready: // Returns again
Do some work
Comme vous le voyez, notre fonction demande au système d'ouvrir un fichier et n'attend pas son ouverture. Il se termine en fournissant les étapes suivantes une fois le fichier prêt. À notre retour, Node exécute d'autres appels de fonction sur la file d'attente. Après avoir parcouru toutes les fonctions, la boucle d'événements passe au tour suivant ...
En résumé, Node a un paradigme complètement différent du développement multi-thread; mais cela ne veut pas dire qu'il manque de choses. Pour un travail synchrone (où nous pouvons décider de l'ordre et du mode de traitement), cela fonctionne aussi bien que le parallélisme multi-thread. Pour un travail qui vient de l'extérieur comme des requêtes à un serveur, c'est tout simplement supérieur.
(1) À moins que vous ne construisiez des bibliothèques dans d'autres langages comme C / C ++, auquel cas vous ne créez toujours pas de threads pour diviser les tâches. Pour ce genre de travail, vous avez deux threads dont l'un continuera à communiquer avec Node tandis que l'autre fera le vrai travail.
(2) En fait, chaque processus Node a plusieurs threads pour les mêmes raisons que j'ai mentionnées dans la première note de bas de page. Cependant, ce n'est pas comme 1000 threads effectuant des travaux similaires. Ces threads supplémentaires servent à accepter des événements d'E / S et à gérer la messagerie inter-processus.
UPDATE (comme réponse à une bonne question dans les commentaires)
@Mark, merci pour la critique constructive. Dans le paradigme de Node, vous ne devriez jamais avoir de fonctions qui prennent trop de temps à traiter à moins que tous les autres appels de la file d'attente ne soient conçus pour être exécutés les uns après les autres. Dans le cas de tâches coûteuses en calcul, si nous regardons l'image dans son intégralité, nous voyons que ce n'est pas une question de "Devrions-nous utiliser des threads ou des processus?" mais une question de "Comment pouvons-nous diviser ces tâches d'une manière bien équilibrée en sous-tâches que nous pouvons les exécuter en parallèle en utilisant plusieurs cœurs de processeur sur le système?" Disons que nous traiterons 400 fichiers vidéo sur un système à 8 cœurs. Si nous voulons traiter un fichier à la fois, nous avons besoin d'un système qui traitera différentes parties du même fichier, auquel cas, peut-être, un système à processus unique multi-thread sera plus facile à construire et encore plus efficace. Nous pouvons toujours utiliser Node pour cela en exécutant plusieurs processus et en passant des messages entre eux lorsque le partage d'état / la communication est nécessaire. Comme je l'ai déjà dit, une approche multi-processus avec Node estainsi qu'une approche multithread dans ce genre de tâches; mais pas plus que ça. Encore une fois, comme je l'ai déjà dit, la situation dans laquelle Node brille est lorsque nous avons ces tâches en tant qu'entrée dans le système à partir de plusieurs sources, car conserver de nombreuses connexions simultanément est beaucoup plus léger dans Node par rapport à un thread par connexion ou un processus par connexion. système.
Quant aux setTimeout(...,0)
appels; Parfois, donner une pause pendant une tâche longue pour permettre aux appels dans la file d'attente d'avoir leur part de traitement peut être nécessaire. La division des tâches de différentes manières peut vous en éviter; mais encore, ce n'est pas vraiment un hack, c'est juste la façon dont les files d'attente d'événements fonctionnent. Aussi, utiliser process.nextTick
pour cet objectif est bien meilleur puisque lorsque vous utilisez setTimeout
, le calcul et les vérifications du temps passé seront nécessaires alors que process.nextTick
c'est simplement ce que nous voulons vraiment: "Hé tâche, retournez à la fin de la file d'attente, vous avez utilisé votre part! "