Concurrencia y subprocesamiento múltiple en JavaScript: nivel avanzado

Entrenador de Python en Línea para Principiantes

Aprende Python fácilmente sin sobrecargarte de teoría. Resuelve tareas prácticas con verificación automática, recibe pistas y escribe código directamente en el navegador, sin instalar nada.

Start Course

Concurrencia y subprocesamiento múltiple en JavaScript: nivel avanzado

\n\n

Tradicionalmente, JavaScript se considera un lenguaje de un solo subproceso, sin embargo, el ecosistema moderno de Node.js proporciona herramientas potentes para trabajar con concurrencia y verdadero subprocesamiento múltiple. En este artículo, analizaremos técnicas avanzadas: desde los detalles del Event Loop hasta el uso de Worker Threads, SharedArrayBuffer y Atomics. Aprenderá a distribuir la carga de manera eficiente, evitar condiciones de carrera y construir aplicaciones escalables.

\n\n

1. Event Loop, microtareas y macrotareas: el fundamento de la concurrencia

\n\n

Comprender el Event Loop es la clave para gestionar la concurrencia sin bloqueos. En Node.js, el ciclo de eventos consta de varias fases: temporizadores, callbacks de I/O, idle, poll, check (setImmediate) y close. Es importante distinguir entre microtareas (Promise, process.nextTick) y macrotareas (setTimeout, setInterval, I/O).

\n\n

Las microtareas se ejecutan inmediatamente después de que finaliza la fase actual, antes de la siguiente macrotarea. Esto puede provocar una "inanición" del Event Loop si las microtareas se generan infinitamente.

\n\n
// Ejemplo: microtareas bloquean macrotareas\nsetTimeout(() => console.log('Macrotarea'), 0);\n\nPromise.resolve().then(() => {\n  // Microtarea infinita\n  process.nextTick(() => {\n    console.log('Microtarea');\n    // La llamada recursiva bloqueará el Event Loop\n    process.nextTick(() => process.nextTick(() => {}));\n  });\n});\n// Salida: 'Microtarea' (infinitamente), 'Macrotarea' nunca se ejecutará
\n\n

Para un control avanzado, use queueMicrotask() y setImmediate(). Recuerde: process.nextTick tiene prioridad sobre las microtareas de Promise.

\n\n

2. Worker Threads: verdadero subprocesamiento múltiple en Node.js

\n\n

El módulo worker_threads permite ejecutar JavaScript en subprocesos paralelos. Cada Worker tiene su propia instancia de V8 y memoria aislada. El intercambio de datos se realiza a través de mensajes o SharedArrayBuffer.

\n\n

Creación de 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 detenido con código ${code}`));\n    });\n  });\n}\n\n// Ejecución\nrunWorker('./worker.js', { iterations: 1e9 })\n  .then(result => console.log('Resultado:', 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

Importante: El Worker no tiene acceso al DOM, a los objetos globales del proceso (process.env) ni a los módulos cargados en el subproceso principal. Transfiera solo datos serializables.

\n\n

Pool de Workers

\n

Crear un Worker para cada tarea es costoso. Use 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