Appearance
Worker Threads
Supposons que vous souhaitiez effectuer une opération mathématique très lourde, comme trouver un grand nombre premier, et que vous l'exécutiez directement dans votre code principal :
javascript
function isPrime(number) {
for (let i = 2, sqrt = Math.sqrt(number); i <= sqrt; i++) {
if (number % i === 0) {
return false;
}
}
return number > 1;
}
function findLargePrime() {
let num = 21474836478458963245;
while (!isPrime(num)) {
num--;
}
console.log(`Le plus grand nombre premier inférieur à 2147483647 est : ${num}`);
}
console.time()
findLargePrime();
console.timeEnd();
function isPrime(number) {
for (let i = 2, sqrt = Math.sqrt(number); i <= sqrt; i++) {
if (number % i === 0) {
return false;
}
}
return number > 1;
}
function findLargePrime() {
let num = 21474836478458963245;
while (!isPrime(num)) {
num--;
}
console.log(`Le plus grand nombre premier inférieur à 2147483647 est : ${num}`);
}
console.time()
findLargePrime();
console.timeEnd();
Lorsque vous exécutez ce code, vous pourriez constater que le message "console.timeEnd()" pourrait prendre beaucoup de temps à s'afficher, voire ne jamais s'afficher si la fonction findLargePrime
prend énormément de temps.
Problème:
La fonction findLargePrime
est une fonction CPU-intensive. Exécutée dans le thread principal, elle bloquera la boucle d'événements jusqu'à ce qu'elle trouve un nombre premier, empêchant toute autre opération asynchrone ou même des tâches simples comme le journalisation d'être exécutées.
Solution:
Pour éviter de telles situations, vous pouvez utiliser des threads de travail ou des techniques pour diviser les tâches longues en plusieurs petites tâches. Dans ce cas, les worker_threads
seraient une solution idéale. Ils permettraient d'exécuter le calcul dans un thread séparé, laissant l'Event Loop du thread principal non bloqué.
worker_threads
dans Node.js
Les worker_threads
permettent l'exécution de code JavaScript en parallèle dans Node.js. Il est utile pour effectuer des opérations CPU-intensives sans bloquer la boucle d'événements.
Comment utiliser les worker_threads
Initialisation
Pour utiliser les
worker_threads
, vous devez d'abord importer les éléments nécessaires du module.javascriptimport { Worker, isMainThread, parentPort } from 'worker_threads';
import { Worker, isMainThread, parentPort } from 'worker_threads';
Déterminer si le code est exécuté dans le thread principal ou dans un worker
La constante
isMainThread
esttrue
si le code est exécuté dans le thread principal etfalse
s'il est exécuté dans un worker.javascriptif (isMainThread) { // Code du thread principal } else { // Code du worker }
if (isMainThread) { // Code du thread principal } else { // Code du worker }
Création d'un nouveau worker
Dans le thread principal, vous pouvez créer un nouveau worker en instanciant le
Worker
avec le chemin de votre fichier script.javascriptconst worker = new Worker('./workerCode.js');
const worker = new Worker('./workerCode.js');
Ce script de worker (
workerCode.js
dans cet exemple) sera exécuté dans un thread séparé.Communication entre le thread principal et le worker
Du thread principal vers le worker : Vous pouvez envoyer des messages au worker à l'aide de la méthode
postMessage
et écouter les messages du worker avec l'événementmessage
.javascriptworker.postMessage('Hello, Worker!'); worker.on('message', (message) => { console.log('Reçu du worker:', message); });
worker.postMessage('Hello, Worker!'); worker.on('message', (message) => { console.log('Reçu du worker:', message); });
Du worker vers le thread principal : Dans le script du worker, vous pouvez utiliser
parentPort
pour envoyer et recevoir des messages.javascriptparentPort.on('message', (message) => { console.log('Reçu du thread principal:', message); parentPort.postMessage('Hello, Main Thread!'); });
parentPort.on('message', (message) => { console.log('Reçu du thread principal:', message); parentPort.postMessage('Hello, Main Thread!'); });
Gestion des erreurs et terminaison
Les workers peuvent émettre des erreurs. Vous pouvez écouter l'événement
error
pour les gérer.javascriptworker.on('error', (error) => { console.error('Erreur dans le worker:', error); });
worker.on('error', (error) => { console.error('Erreur dans le worker:', error); });
Une fois qu'un worker a terminé son travail, vous pouvez le terminer explicitement ou écouter l'événement
exit
.javascriptworker.on('exit', (code) => { if (code !== 0) { console.error(`Worker stopped with exit code ${code}`); } });
worker.on('exit', (code) => { if (code !== 0) { console.error(`Worker stopped with exit code ${code}`); } });
Exemple
Supposons que vous souhaitiez utiliser un worker pour calculer la somme des nombres jusqu'à un certain nombre.
main.js :
javascript
import { Worker } from 'worker_threads';
const worker = new Worker('./sumWorker.js');
worker.on('message', (sum) => {
console.log(`Somme: ${sum}`);
});
worker.postMessage(1000000);
import { Worker } from 'worker_threads';
const worker = new Worker('./sumWorker.js');
worker.on('message', (sum) => {
console.log(`Somme: ${sum}`);
});
worker.postMessage(1000000);
sumWorker.js :
javascript
import { parentPort } from 'worker_threads';
parentPort.on('message', (max) => {
let sum = 0;
for (let i = 0; i <= max; i++) {
sum += i;
}
parentPort.postMessage(sum);
});
import { parentPort } from 'worker_threads';
parentPort.on('message', (max) => {
let sum = 0;
for (let i = 0; i <= max; i++) {
sum += i;
}
parentPort.postMessage(sum);
});
Lorsque vous exécutez main.js
, il enverra le nombre 1000000
au worker. Le worker calculera la somme des nombres jusqu'à ce nombre et renverra le résultat au thread principal.
Les worker_threads
sont un excellent moyen de paralléliser des tâches intensives en CPU et d'optimiser l'utilisation des ressources de votre machine, tout en gardant la boucle d'événements de votre application Node.js non bloquée.
Les limitations
L'utilisation des worker_threads
dans Node.js vient avec plusieurs avantages, principalement liés à la parallélisation des tâches intensives en CPU. Cependant, il y a aussi des limitations et des précautions à prendre en compte :
Mémoire partagée limitée : Contrairement à d'autres environnements multithread, chaque worker dans Node.js a sa propre mémoire. Les données ne peuvent pas être partagées directement entre les threads. Au lieu de cela, les données sont échangées via des messages, ce qui signifie qu'elles sont sérialisées et désérialisées, ce qui peut avoir un coût en performance pour de grandes quantités de données.
Overhead de création : Il y a un coût associé à la création d'un nouveau thread. Si un worker est utilisé pour une petite tâche qui est exécutée rapidement, l'overhead de création du thread peut surpasser les avantages d'exécuter cette tâche hors du thread principal.
Complexité accrue : La gestion de plusieurs threads peut rendre le code plus complexe, surtout en ce qui concerne la coordination entre les threads, la gestion des erreurs, etc.
Limitation des ressources : Même si chaque worker s'exécute dans un thread distinct, le nombre de threads que vous pouvez créer est limité par les ressources système (par exemple, la mémoire ou le nombre maximal de threads). Créer un grand nombre de threads peut saturer les ressources et nuire aux performances.
Pas de garantie d'ordre d'exécution : Puisque les workers s'exécutent en parallèle, il n'y a aucune garantie sur l'ordre d'exécution ou de finition des tâches. Vous devez être prudent lorsque vous dépendez de l'ordre des opérations entre les threads.
Modules natifs : Certains modules natifs de Node.js peuvent ne pas être thread-safe. Si un module natif n'est pas conçu pour être utilisé avec
worker_threads
, cela pourrait conduire à des comportements imprévisibles ou à des erreurs.Accès limité aux modules globaux : Dans un worker, certains objets et fonctions globaux du thread principal ne sont pas disponibles. Par exemple,
console
est disponible, maisprocess.stdout
ne l'est pas.Communication inter-thread : La communication entre le thread principal et les workers est basée sur un système de messagerie. Bien que cela isole bien chaque thread, cela signifie également que les structures de données complexes doivent être copiées ou transférées entre les threads, ce qui peut augmenter l'overhead.
Modules spécifiques au thread principal : Certains modules ou fonctionnalités, comme l'accès à la boucle d'événements, ne sont accessibles qu'à partir du thread principal.