Maison  >  Article  >  interface Web  >  Comment comprendre que Node.js n'est pas un programme entièrement monothread (une brève analyse)

Comment comprendre que Node.js n'est pas un programme entièrement monothread (une brève analyse)

青灯夜游
青灯夜游avant
2022-02-08 18:20:251801parcourir

Pourquoi disons-nous que Node.js n'est pas complètement monothread ? Comment comprendre ? L’article suivant en discutera avec vous, j’espère qu’il vous sera utile !

Comment comprendre que Node.js n'est pas un programme entièrement monothread (une brève analyse)

Je crois que tout le monde sait que node est un programme monothread qui utilise Event Loop pour obtenir plusieurs simultanéités. Malheureusement, ce n'est pas tout à fait exact.

Alors pourquoi Node.js n'est-il pas un programme entièrement monothread ?

Node.js est un programme à thread unique*

Toutes les boucles Javsacript, V8 et d'événements que nous avons nous-mêmes écrites s'exécutent dans le même thread, qui est le thread principal.

Hé, cela ne signifie-t-il pas que ce nœud est monothread ?

Mais peut-être que vous ne savez pas que ce nœud contient de nombreux modules avec du code C++ derrière eux.

Bien que le nœud n'expose pas les utilisateurs à l'autorisation de contrôler les threads, C++ peut utiliser le multi-threading.

Alors, quand node utilisera-t-il le multi-threading ?

  • Si une méthode de nœud appelle la méthode synchronisation de C++ en arrière-plan, tout s'exécutera dans le thread principal.

  • Si une méthode de nœud appelle la méthode asynchrone de C++ en arrière-plan, parfois elle ne s'exécute pas dans le thread principal.

Talk n'est pas cher, montre-moi le code

Méthode synchrone, exécutée dans le thread principal

Ici crypto Modules associés, beaucoup sont écrits en C++. Le programme suivant est une fonction de calcul de hachage, généralement utilisée pour stocker les mots de passe.

import { pbkdf2Sync } from "crypto";
const startTime = Date.now();
let index = 0;
for (index = 0; index < 3; index++) {
    pbkdf2Sync("secret", "salt", 100000, 64, "sha512");
    const endTime = Date.now();
    console.log(`${index} time, ${endTime - startTime}`);
}
const endTime = Date.now();
console.log(`in the end`);

Temps de sortie,

0 time, 44 
1 time, 90
2 time, 134
in the end

peut être vu que cela prend environ 45 ms à chaque fois et que le code est exécuté séquentiellement sur le thread principal.

Faites attention à qui est le résultat final ? Notez qu'un hachage prend ici environ 45 ms sur mon processeur.

La méthode asynchrone pbkdf2 ne s'exécute pas dans le thread principal

import { cpus } from "os";
import { pbkdf2 } from "crypto";
console.log(cpus().length);
let startTime = console.time("time-main-end");
for (let index = 0; index < 4; index++) {
    startTime = console.time(`time-${index}`);
    pbkdf2("secret", `salt${index}`, 100000, 64, "sha512", (err, derivedKey) => {
        if (err) throw err;
        console.timeEnd(`time-${index}`);
    });
}
console.timeEnd("time-main-end");

Le temps de sortie,

time-main-end: 0.31ms
time-2: 45.646ms
time-0: 46.055ms
time-3: 46.846ms
time-1: 47.159ms

Comme vous pouvez le voir ici, le thread principal se termine plus tôt, mais chaque temps de calcul est de 45 ms. Le processeur calcule le hachage. Le temps est de 45 ms. Le nœud ici utilise définitivement plusieurs threads pour le calcul du hachage.

Si je change le nombre d'appels ici à 10, alors le temps est le suivant. Vous pouvez voir qu'à mesure que le nombre de cœurs de processeur est utilisé, le temps augmente également. Une fois de plus, il est prouvé que le nœud utilise définitivement plusieurs threads pour le calcul du hachage.

time-main-end: 0.451ms
time-1: 44.977ms
time-2: 46.069ms
time-3: 50.033ms
time-0: 51.381ms
time-5: 96.429ms // 注意这里,从第五次时间开始增加了
time-7: 101.61ms
time-4: 113.535ms
time-6: 121.429ms
time-9: 151.035ms
time-8: 152.585ms

Bien qu'il soit prouvé ici que le multi-threading est définitivement activé sur le nœud. Mais il y a un petit problème ? Le processeur de mon ordinateur est AMD R5-5600U, doté de 6 cœurs et 12 threads. Mais pourquoi le temps augmente-t-il à partir de la cinquième fois ? Le nœud n’utilise pas pleinement mon processeur ?

Quelle est la raison ?

Node utilise un pool de threads prédéfini. La taille par défaut de ce pool de threads est 4.

export UV_THREADPOOL_SIZE=6

Regardons un exemple,

Requête HTTP

import { request } from "https";
const options = {
  hostname: "www.baidu.com",
  port: 443,
  path: "/img/PC_7ac6a6d319ba4ae29b38e5e4280e9122.png",
  method: "GET",
};

let startTime = console.time(`main`);

for (let index = 0; index < 15; index++) {
  startTime = console.time(`time-${index}`);
  const req = request(options, (res) => {
    console.log(`statusCode: ${res.statusCode}`);
    console.timeEnd(`time-${index}`);
    res.on("data", (d) => {
      // process.stdout.write(d);
    });
  });

  req.on("error", (error) => {
    console.error(error);
  });

  req.end();
}

console.timeEnd("main");
main: 13.927ms
time-2: 83.247ms
time-4: 89.641ms
time-3: 91.497ms
time-12: 91.661ms
time-5: 94.677ms
.....
time-8: 134.026ms
time-1: 143.906ms
time-13: 140.914ms
time-10: 144.088ms

Le programme principal ici est Cela s'est également terminé plus tôt. Ici, j'ai lancé la requête http pour télécharger des images 15 fois. Le temps nécessaire n'a pas augmenté de façon exponentielle et ne semblait pas être limité par le pool de threads/le processeur.

Pourquoi ? ? Node utilise-t-il un pool de threads ?

Si la méthode C++ asynchrone derrière Node, elle essaiera d'abord de voir s'il existe un support asynchrone du noyau. Par exemple, veuillez utiliser epoll (Linux) pour le réseau ici. Si le noyau ne fournit pas de méthode asynchrone, Node. utilisera son propre pool de threads. .

Ainsi, bien que la requête http soit asynchrone, elle est implémentée par le noyau lorsque le noyau est terminé, C++ en sera informé et C++ notifiera au thread principal de gérer le rappel.

Alors, quelles méthodes asynchrones dans Node utilisent le pool de threads ? Lesquels ne le feront pas ?

  • Native Kernal Async

    • Client serveur TCP/UDP
    • Unix Domain Sockets (IPC)
    • pipes
    • dns.resolveXXX
    • tty input (stdin etc)
    • Signaux Unix
    • Processus enfant
  • Thread pool

    • fs.*
    • dns.lookup
    • pipe (edge ​​​​case)

C'est également le point d'entrée pour la plupart des optimisations de nœuds.

Mais comment ceux-ci se combinent-ils avec la boucle d'événement la plus importante ?

Event Loop

Je pense que tout le monde connaît très bien Event Loop. La boucle d'événements est comme un distributeur.

  • Si elle rencontre un programme javascript ordinaire ou un rappel, elle est transmise à V8 pour traitement.

  • Si vous rencontrez une méthode synchronisée écrite en C++, remettez-la en C++ et exécutez-la sur le thread principal.

  • Si vous rencontrez une méthode asynchrone, le verso est écrit en C++. S'il existe un support asynchrone du noyau, transmettez-le du thread principal au noyau pour traitement.

  • Si elle est asynchronele dos de la méthode est écrit en C++ S'il n'y a pas de support asynchrone par le noyau, elle est transmise du thread principal au pool de threads.

  • Le pool de threads et le noyau renverront les résultats à la boucle d'événements. Si un rappel javascript est enregistré, il sera transmis à la V8 pour traitement.

Ensuite, le cycle continue jusqu'à ce qu'il n'y ait plus rien à traiter.

Donc, Node n'est pas exactement un programme monothread.

Pour plus de connaissances sur les nœuds, veuillez visiter : tutoriel Nodejs !

Ce qui précède est le contenu détaillé de. pour plus d'informations, suivez d'autres articles connexes sur le site Web de PHP en chinois!

Déclaration:
Cet article est reproduit dans:. en cas de violation, veuillez contacter admin@php.cn Supprimer