Maison > interface Web > js tutoriel > le corps du texte

Éléments internes du nœud JS

Mary-Kate Olsen
Libérer: 2024-11-19 08:15:03
original
366 Les gens l'ont consulté

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.

Node JS est-il monothread ?

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`);
});

Copier après la connexion
Copier après la connexion
Copier après la connexion

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
Copier après la connexion
Copier après la connexion
Copier après la connexion

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`);
});

Copier après la connexion
Copier après la connexion
Copier après la connexion

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
Copier après la connexion
Copier après la connexion
Copier après la connexion

Node JS Internals

Supposons que les opérations de lecture 1 et 2 soient terminées et que les threads 1 et 2 deviennent libres.

Node JS Internals

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.

  • Code synchrone (V8) :

Node.js exécute tout le code synchrone (bloquant) ligne par ligne à l'aide du moteur JavaScript V8.

  • Tâches asynchrones déléguées :

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).

  • Exécution des tâches :

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.

  • Rappel en file d'attente :

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énement exécute les rappels :

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`);
});

Copier après la connexion
Copier après la connexion
Copier après la connexion

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
Copier après la connexion
Copier après la connexion
Copier après la connexion

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`);
});
Copier après la connexion

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!

source:dev.to
Déclaration de ce site Web
Le contenu de cet article est volontairement contribué par les internautes et les droits d'auteur appartiennent à l'auteur original. Ce site n'assume aucune responsabilité légale correspondante. Si vous trouvez un contenu suspecté de plagiat ou de contrefaçon, veuillez contacter admin@php.cn
Derniers articles par auteur
Tutoriels populaires
Plus>
Derniers téléchargements
Plus>
effets Web
Code source du site Web
Matériel du site Web
Modèle frontal