Concurrence et multithreading en JavaScript : niveau avancé

Formateur Python en ligne pour débutants

Apprenez Python facilement sans surcharge théorique. Résolvez des tâches pratiques avec vérification automatique et écrivez du code directement dans le navigateur.

Start Course

Concurrence et multithreading en JavaScript : niveau avancé

\n\n

JavaScript est traditionnellement considéré comme un langage monothread, mais l'écosystème moderne de Node.js offre des outils puissants pour travailler avec la concurrence et le véritable multithreading. Dans cet article, nous aborderons des techniques avancées : des subtilités de l'Event Loop à l'utilisation de Worker Threads, SharedArrayBuffer et Atomics. Vous apprendrez à répartir efficacement la charge, à éviter les conditions de course et à construire des applications évolutives.

\n\n

1. Event Loop, microtasks et macrotasks : le fondement de la concurrence

\n\n

Comprendre l'Event Loop est la clé pour gérer la concurrence sans blocages. Dans Node.js, la boucle d'événements se compose de plusieurs phases : temporisateurs, callbacks I/O, idle, poll, check (setImmediate) et close. Il est important de distinguer les microtasks (Promise, process.nextTick) des macrotasks (setTimeout, setInterval, I/O).

\n\n

Les microtasks s'exécutent immédiatement après la fin de la phase en cours, avant la macrotask suivante. Cela peut entraîner une « famine » de l'Event Loop si les microtasks sont générées à l'infini.

\n\n
// Exemple : les microtasks bloquent les macrotasks\nsetTimeout(() => console.log('Macrotask'), 0);\n\nPromise.resolve().then(() => {\n  // Microtask infinie\n  process.nextTick(() => {\n    console.log('Microtask');\n    // L'appel récursif bloquera l'Event Loop\n    process.nextTick(() => process.nextTick(() => {}));\n  });\n});\n// Sortie : 'Microtask' (à l'infini), 'Macrotask' ne s'exécutera jamais
\n\n

Pour un contrôle avancé, utilisez queueMicrotask() et setImmediate(). Rappelez-vous : process.nextTick a la priorité sur les microtasks Promise.

\n\n

2. Worker Threads : le véritable multithreading dans Node.js

\n\n

Le module worker_threads permet d'exécuter JavaScript dans des threads parallèles. Chaque Worker possède sa propre instance V8 et une mémoire isolée. L'échange de données se fait via des messages ou SharedArrayBuffer.

\n\n

Création d'un Worker simple :

\n\n
// main.js\nconst { Worker } = require('worker_threads');\n\nfunction runWorker(file, data) {\n  return new Promise((resolve, reject) => {\n    const worker = new Worker(file, { workerData: data });\n    worker.on('message', resolve);\n    worker.on('error', reject);\n    worker.on('exit', (code) => {\n      if (code !== 0) reject(new Error(`Worker arrêté avec le code ${code}`));\n    });\n  });\n}\n\n// Lancement\nrunWorker('./worker.js', { iterations: 1e9 })\n  .then(result => console.log('Résultat :', result))\n  .catch(err => console.error(err));
\n\n
// worker.js\nconst { parentPort, workerData } = require('worker_threads');\n\nfunction heavyComputation(n) {\n  let sum = 0;\n  for (let i = 0; i < n; i++) { sum += Math.sqrt(i); }\n  return sum;\n}\n\nparentPort.postMessage(heavyComputation(workerData.iterations));
\n\n

Important : Le Worker n'a pas accès au DOM, aux objets globaux du processus (process.env) ni aux modules chargés dans le thread principal. Transmettez uniquement des données sérialisables.

\n\n

Pool de Workers

\n

Créer un Worker pour chaque tâche est coûteux. Utilisez un pool :

\n\n
class WorkerPool {\n  constructor(file, size = 4) {\n    this.workers = [];\n    this.queue = [];\n    for (let i = 0; i < size; i++) {\n      const worker = new Worker(file);\n      worker.on('message', (result) => this._handleResult(worker, result));\n      worker.on('error', (err) => this._handleError(worker, err));\n      this.workers.push(worker);\n    }\n  }\n\n  runTask(data) {\n    return new Promise((resolve, reject) => {\n      this.queue.push({ data, resolve, reject });\n      this._processQueue();\n    });\n  }\n\n  _processQueue() {\n    if (this.queue.length === 0) return;\n    const worker = this.workers.find(w => !w.busy);\n    if (!worker) return;\n    worker.busy = true;\n    const task = this.queue.shift();                

Blogs

Book Recommendations