Supposons que vous alliez au restaurant et qu'il y ait un seul chef qui promet que "je peux cuisiner pour des centaines de personnes en même temps et qu'aucun d'entre vous n'aura faim". Cela semble impossible, n'est-ce pas ? Vous pouvez considérer ce chèque unique comme Node JS qui gère toutes ces commandes multiples et sert toujours la nourriture à tous les clients.
Chaque fois que vous posez à quelqu'un la question « Qu'est-ce que Node JS ? », la personne répond toujours « Node JS est un moteur d'exécution qui est utilisé pour exécuter JavaScript en dehors de l'environnement du navigateur ».
Mais que signifie l'exécution ?... L'environnement d'exécution est une infrastructure logicielle dans laquelle l'exécution du code est écrite dans un langage de programmation spécifique. Il dispose de tous les outils, bibliothèques et fonctionnalités nécessaires pour exécuter du code, gérer les erreurs, gérer la mémoire et peut interagir avec le système d'exploitation ou le matériel sous-jacent.
Node JS a tout cela.
Google V8 Engine pour exécuter le code.
Bibliothèques de base et API telles que fs, crypto, http, etc.
Infrastructure comme Libuv et Event Loop pour prendre en charge les opérations d'E/S asynchrones et non bloquantes.
Nous pouvons donc maintenant savoir pourquoi Node JS est appelé runtime.
Ce runtime se compose de deux dépendances indépendantes, V8 et libuv.
V8 est un moteur également utilisé dans Google Chrome et il est développé et géré par Google. Dans Node JS, il exécute le code JavaScript. Lorsque nous exécutons la commande node index.js alors Node JS transmet ce code au moteur V8. V8 traite ce code, l'exécute et fournit le résultat. Par exemple, si votre code enregistre « Hello, World ! » à la console, V8 gère l'exécution réelle qui permet que cela se produise.
La bibliothèque libuv contient le code C qui permet d'accéder au système d'exploitation lorsque nous voulons des fonctionnalités telles que la mise en réseau, les opérations d'E/S ou les opérations liées au temps. Il fonctionne comme un pont entre Node JS et le système d'exploitation.
La libuv gère les opérations suivantes :
Opérations sur le système de fichiers : lecture ou écriture de fichiers (fs.readFile, fs.writeFile).
Mise en réseau : gestion des requêtes HTTP, des sockets ou de la connexion aux serveurs.
Timers : Gestion de fonctions comme setTimeout ou setInterval.
Les tâches telles que la lecture de fichiers sont gérées par le pool de threads Libuv, les minuteries par le système de minuterie de Libuv et les appels réseau par les API au niveau du système d'exploitation.
Regardez l'exemple suivant.
const fs = require('fs'); const path = require('path'); const filePath = path.join(__dirname, 'file.txt'); const readFileWithTiming = (index) => { const start = Date.now(); fs.readFile(filePath, 'utf8', (err, data) => { if (err) { console.error(`Error reading the file for task ${index}:`, err); return; } const end = Date.now(); console.log(`Task ${index} completed in ${end - start}ms`); }); }; const startOverall = Date.now(); for (let i = 1; i <= 4; i++) { readFileWithTiming(i); } process.on('exit', () => { const endOverall = Date.now(); console.log(`Total execution time: ${endOverall - startOverall}ms`); });
Nous lisons le même fichier quatre fois et nous enregistrons le temps de lecture de ces fichiers.
Nous obtenons le résultat suivant de ce code.
Task 1 completed in 50ms Task 2 completed in 51ms Task 3 completed in 52ms Task 4 completed in 53ms Total execution time: 54ms
Nous pouvons voir que nous avons terminé la lecture des quatre fichiers presque à la 50ème ms. Si Node JS est monothread, comment toutes ces opérations de lecture de fichiers sont-elles effectuées en même temps ?
Cette question répond que la bibliothèque libuv utilise le pool de threads. le pool de threads est un groupe de threads. Par défaut, la taille du pool de threads est de 4, ce qui signifie que 4 requêtes peuvent être traitées à la fois par libuv.
Considérons un autre scénario dans lequel, au lieu de lire un fichier 4 fois, nous lisons ce fichier 6 fois.
const fs = require('fs'); const path = require('path'); const filePath = path.join(__dirname, 'file.txt'); const readFileWithTiming = (index) => { const start = Date.now(); fs.readFile(filePath, 'utf8', (err, data) => { if (err) { console.error(`Error reading the file for task ${index}:`, err); return; } const end = Date.now(); console.log(`Task ${index} completed in ${end - start}ms`); }); }; const startOverall = Date.now(); for (let i = 1; i <= 4; i++) { readFileWithTiming(i); } process.on('exit', () => { const endOverall = Date.now(); console.log(`Total execution time: ${endOverall - startOverall}ms`); });
Le résultat ressemblera à :
Task 1 completed in 50ms Task 2 completed in 51ms Task 3 completed in 52ms Task 4 completed in 53ms Total execution time: 54ms
Supposons que les opérations de lecture 1 et 2 soient terminées et que les threads 1 et 2 deviennent libres.
Vous pouvez voir que les 4 premières fois, nous obtenons presque le même temps pour lire le fichier, mais lorsque nous lisons ce fichier la 5ème et la 6ème fois, il faut presque le double du temps pour terminer les opérations de lecture des quatre premières opérations de lecture. .
Cela se produit parce que la taille du pool de threads est par défaut de 4, donc quatre opérations de lecture sont gérées en même temps, mais là encore 2 (5ème et 6ème) fois nous lisons le fichier, puis libuv attend car tous les threads ont du travail. Lorsque l'un des quatre threads termine l'exécution, la cinquième opération de lecture est effectuée sur ce thread et la même chose pour la sixième opération de lecture sera effectuée. c'est la raison pour laquelle cela prend plus de temps.
Donc, Node JS n'est pas monothread.
Mais pourquoi certaines personnes l'appellent-elles à thread unique ?
C'est parce que la boucle de l'événement principal est monothread. Ce thread est responsable de l'exécution du code Node JS, notamment de la gestion des rappels asynchrones et de la coordination des tâches. Il ne gère pas directement les opérations de blocage comme les E/S de fichiers.
Le flux d'exécution du code est comme ça.
Node.js exécute tout le code synchrone (bloquant) ligne par ligne à l'aide du moteur JavaScript V8.
Les opérations asynchrones telles que les requêtes fs.readFile, setTimeout ou http sont envoyées à la bibliothèque Libuv ou à d'autres sous-systèmes (par exemple, le système d'exploitation).
Les tâches telles que la lecture de fichiers sont gérées par le pool de threads Libuv, les minuteries par le système de minuterie de Libuv et les appels réseau par les API au niveau du système d'exploitation.
Une fois qu'une tâche asynchrone est terminée, son rappel associé est envoyé à la file d'attente de la boucle d'événements.
La boucle d'événements récupère les rappels de la file d'attente et les exécute un par un, garantissant une exécution non bloquante.
Vous pouvez modifier la taille du pool de threads en utilisant process.env.UV_THREADPOOL_SIZE = 8.
Maintenant, je pense que si nous définissons un nombre élevé de threads, nous pourrons également gérer le nombre élevé de requêtes. J'espère que vous penserez comme moi à ce sujet.
Mais, c'est le contraire de ce que nous pensions.
Si nous augmentons le nombre de threads au-delà d'une certaine limite, cela ralentira l'exécution de votre code.
Regardez l'exemple suivant.
const fs = require('fs'); const path = require('path'); const filePath = path.join(__dirname, 'file.txt'); const readFileWithTiming = (index) => { const start = Date.now(); fs.readFile(filePath, 'utf8', (err, data) => { if (err) { console.error(`Error reading the file for task ${index}:`, err); return; } const end = Date.now(); console.log(`Task ${index} completed in ${end - start}ms`); }); }; const startOverall = Date.now(); for (let i = 1; i <= 4; i++) { readFileWithTiming(i); } process.on('exit', () => { const endOverall = Date.now(); console.log(`Total execution time: ${endOverall - startOverall}ms`); });
sortie :
Avec une taille de pool de threads élevée (100 threads)
Task 1 completed in 50ms Task 2 completed in 51ms Task 3 completed in 52ms Task 4 completed in 53ms Total execution time: 54ms
Maintenant, le résultat suivant est lorsque nous définissons la taille du pool de threads sur 4 (taille par défaut).
Avec la taille du pool de threads par défaut (4 threads)
const fs = require('fs'); const path = require('path'); const filePath = path.join(__dirname, 'file.txt'); const readFileWithTiming = (index) => { const start = Date.now(); fs.readFile(filePath, 'utf8', (err, data) => { if (err) { console.error(`Error reading the file for task ${index}:`, err); return; } const end = Date.now(); console.log(`Task ${index} completed in ${end - start}ms`); }); }; const startOverall = Date.now(); for (let i = 1; i <= 6; i++) { readFileWithTiming(i); } process.on('exit', () => { const endOverall = Date.now(); console.log(`Total execution time: ${endOverall - startOverall}ms`); });
Vous pouvez voir que le temps d'exécution total a une différence de 100 ms. le temps d'exécution total (taille du pool de threads 4) est de 600 ms et le temps d'exécution total (taille du pool de threads 100) est de 700 ms. ainsi, une taille de pool de threads de 4 prend moins de temps.
Pourquoi le nombre élevé de threads != plus de tâches peuvent être traitées simultanément ?
La première raison est que chaque thread a sa propre pile et ses propres besoins en ressources. Si vous augmentez le nombre de threads, cela entraîne finalement un manque de mémoire ou des ressources CPU.
La deuxième raison est que les systèmes d'exploitation doivent planifier les threads. S'il y a trop de threads, le système d'exploitation passera beaucoup de temps à basculer entre eux (changement de contexte), ce qui ajoute une surcharge et ralentit les performances au lieu de les améliorer.
Maintenant, nous pouvons dire qu'il ne s'agit pas d'augmenter la taille du pool de threads pour obtenir une évolutivité et des performances élevées, mais qu'il s'agit d'utiliser la bonne architecture, telle que le clustering, et de comprendre la nature de la tâche (E/S vs CPU-bound). ) et comment fonctionne le modèle événementiel de Node.js.
Merci d'avoir lu.
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!