What is ThreadX OS Simulator?

Paralelismo en JavaScript: Guía Completa

23/03/2006

Valoración: 4.18 (3179 votos)

Históricamente, JavaScript ha sido conocido por su naturaleza de hilo único. Esto significa que, por diseño, solo puede ejecutar una tarea a la vez en una página web. Durante años, esta característica fue suficiente para la web interactiva, pero a medida que las aplicaciones se volvieron más complejas y demandantes, la necesidad de procesar tareas pesadas sin congelar la interfaz de usuario se hizo evidente. Antes de la llegada de HTML5, los desarrolladores recurrían a ingeniosas simulaciones para lograr una ejecución asíncrona, pero hoy en día contamos con herramientas nativas para un verdadero paralelismo. En este artículo, exploraremos a fondo el viaje desde la simulación hasta el multi-hilo real en JavaScript.

How to simulate multi-threading?
By creating multiple interpreters and controlling their execution from the main thread, you can simulate multi-threading with each thread running in its own environment. The approach is somewhat similar to web workers, but you give the interpreter access to the browser global environment. I made a small project to demonstrate this.
Índice de Contenido

El Modelo de Concurrencia y el Event Loop

Para entender por qué necesitamos el multi-hilo, primero debemos comprender cómo JavaScript maneja las tareas. A pesar de ser de hilo único, JavaScript es excepcionalmente bueno en la gestión de la asincronía gracias a su modelo de concurrencia, basado en el famoso event loop (bucle de eventos).

El motor de JavaScript tiene tres componentes clave:

  • Call Stack (Pila de llamadas): Aquí es donde se apilan las funciones a medida que se llaman. Es una estructura LIFO (Last-In, First-Out). Cuando se ejecuta una función, se añade a la cima de la pila; cuando termina, se saca.
  • Web APIs: Son proporcionadas por el navegador (como setTimeout, peticiones AJAX, eventos del DOM). Cuando ejecutamos una operación asíncrona, el motor de JavaScript la delega a la Web API correspondiente y continúa con la siguiente tarea en la pila.
  • Callback Queue (Cola de callbacks): Una vez que la operación asíncrona de la Web API finaliza, su función de callback se coloca en esta cola, esperando su turno para ser ejecutada.

El event loop es el vigilante que constantemente comprueba si la pila de llamadas está vacía. Si lo está, toma la primera función de la cola de callbacks y la empuja a la pila para su ejecución. Este modelo es increíblemente eficiente para operaciones de E/S (Entrada/Salida), como peticiones de red, pero tiene un gran inconveniente: si una tarea en la pila de llamadas tarda mucho en completarse (un cálculo matemático complejo, por ejemplo), bloquea todo el hilo, congelando la interfaz de usuario y creando una experiencia de usuario pésima.

Simulando el Multi-hilo: Técnicas Clásicas

Antes de tener una solución nativa para el paralelismo, los desarrolladores idearon formas de simular la ejecución de múltiples tareas.

Emulación con setTimeout

Una técnica común es usar setTimeout(callback, 0). Esto no ejecuta el callback inmediatamente, sino que lo envía a las Web APIs, que a su vez lo colocan en la cola de callbacks. Esto permite que el hilo principal se libere momentáneamente para procesar otras tareas, como las interacciones del usuario. Podemos ver un ejemplo de cómo simular tres "hilos" que cuentan de forma concurrente:

var MAX_VALUE = 10000;function thread1(value, maxValue){ var me = this; document.getElementById("result1").innerHTML = value; value++; // Continuar la ejecución si no hemos llegado al máximo if(value <= maxValue) { setTimeout(function () { me.thread1(value, maxValue); }, 0); }}function thread2(value, maxValue){ var me = this; document.getElementById("result2").innerHTML = value; value++; if(value <= maxValue) { setTimeout(function () { me.thread2(value, maxValue); }, 0); }}function thread3(value, maxValue){ var me = this; document.getElementById("result3").innerHTML = value; value++; if(value <= maxValue) { setTimeout(function () { me.thread3(value, maxValue); }, 0); }}thread1(0, MAX_VALUE);thread2(0, MAX_VALUE);thread3(0, MAX_VALUE);

Aunque parece que las tareas se ejecutan en paralelo, en realidad están compartiendo el único hilo disponible. Cada llamada a setTimeout cede el control por un instante, pero si la porción de trabajo dentro de cada función es pesada, el navegador seguirá ralentizándose e incluso podría congelarse. Es concurrencia, no verdadero paralelismo.

Generadores y yield (ECMAScript 6)

Con la llegada de ES6, los generadores ofrecieron una nueva forma de controlar el flujo de ejecución. Una función generadora (definida con function*) puede pausar su ejecución usando la palabra clave yield y reanudarla más tarde. Esto permite una forma de multitarea cooperativa, donde una función cede voluntariamente el control. Aunque es una herramienta poderosa para crear iteradores complejos y manejar flujos asíncronos (fue un precursor de async/await), su uso para simular multi-hilo es complejo y ha sido superado por soluciones más modernas y directas.

La Verdadera Solución: Web Workers de HTML5

La introducción de HTML5 trajo consigo la solución definitiva al problema del bloqueo del hilo principal: los Web Workers. Un Web Worker es, en esencia, un script de JavaScript que se ejecuta en un hilo de fondo, completamente separado del hilo principal que maneja la interfaz de usuario. Esto permite realizar cálculos intensivos y tareas pesadas sin afectar en lo más mínimo la capacidad de respuesta de la página.

How to simulate multi-threading?
By creating multiple interpreters and controlling their execution from the main thread, you can simulate multi-threading with each thread running in its own environment. The approach is somewhat similar to web workers, but you give the interpreter access to the browser global environment. I made a small project to demonstrate this.

¿Cómo funcionan los Web Workers?

La comunicación entre el hilo principal y un worker se realiza a través de un sistema de mensajes. No pueden acceder directamente al DOM (objetos document o window), lo que garantiza que no haya conflictos de concurrencia al manipular la interfaz.

  • Hilo Principal: Crea el worker y le envía datos usando el método worker.postMessage().
  • Worker Thread: Escucha los mensajes con self.addEventListener('message', ...), procesa los datos y devuelve el resultado al hilo principal usando self.postMessage().
  • Hilo Principal: Escucha los resultados del worker con worker.addEventListener('message', ...) y actualiza la UI si es necesario.

Aquí tienes un ejemplo práctico con tres workers que cuentan hasta un valor máximo de forma simultánea, demostrando un paralelismo real:

// Función auxiliar para crear un Worker a partir de una función en lugar de un archivo externo.function getScriptPath(foo) { return window.URL.createObjectURL(new Blob([ foo.toString().match(/^\s*function\s*\(\s*\)\s*\{(([\s\S](?!\}$))*[\s\S])/)[1] ], { type: 'text/javascript' }));}var MAX_VALUE = 10000;// --- Worker 1 ---var worker1 = new Worker(getScriptPath(function () { self.addEventListener('message', function (e) { var value = 0; while (value <= e.data) { self.postMessage(value); value++; } }, false);}));worker1.addEventListener('message', function (e) { document.getElementById("result1").innerHTML = e.data;}, false);// --- Worker 2 ---var worker2 = new Worker(getScriptPath(function () { self.addEventListener('message', function (e) { var value = 0; while (value <= e.data) { self.postMessage(value); value++; } }, false);}));worker2.addEventListener('message', function (e) { document.getElementById("result2").innerHTML = e.data;}, false);// --- Worker 3 ---var worker3 = new Worker(getScriptPath(function () { self.addEventListener('message', function (e) { var value = 0; while (value <= e.data) { self.postMessage(value); value++; } }, false);}));worker3.addEventListener('message', function (e) { document.getElementById("result3").innerHTML = e.data;}, false);// Iniciar los workers enviándoles el valor máximo a contarworker1.postMessage(MAX_VALUE);worker2.postMessage(MAX_VALUE);worker3.postMessage(MAX_VALUE);

Al ejecutar este código, verás que los tres contadores se actualizan simultáneamente sin que la página se vuelva lenta o deje de responder. Este es el poder del verdadero multi-hilo en el navegador.

Tabla Comparativa de Métodos

Para aclarar las diferencias, aquí tienes una tabla que resume las características de cada enfoque:

CaracterísticasetTimeoutWeb Workers
Tipo de EjecuciónConcurrencia (un solo hilo)Paralelismo (múltiples hilos)
Acceso al DOMSí, acceso completoNo, comunicación por mensajes
Impacto en la UIPuede bloquear la UI si las tareas son largasNinguno, se ejecuta en segundo plano
Caso de Uso IdealTareas cortas y no bloqueantes, animacionesCálculos intensivos, procesamiento de datos, criptografía
ComplejidadBajaModerada (requiere gestión de mensajes)

Preguntas Frecuentes (FAQ)

¿Web Workers y `async/await` son lo mismo?

No. Son conceptos diferentes que resuelven problemas distintos. async/await es una sintaxis para manejar código asíncrono (como promesas) de una manera más legible y secuencial, pero sigue ejecutándose en el hilo principal. Los Web Workers, en cambio, ejecutan código en un hilo completamente separado. Se puede usar async/await dentro de un Web Worker, pero no son sustitutos el uno del otro.

¿Puedo usar librerías externas dentro de un Web Worker?

Sí. Los Web Workers tienen una función global llamada importScripts() que permite cargar y ejecutar scripts externos dentro del ámbito del worker. Por ejemplo: importScripts('https://cdnjs.cloudflare.com/ajax/libs/moment.js/2.29.1/moment.min.js');

¿Cuál es el costo de crear un Web Worker?

Crear un worker tiene un costo en términos de memoria y rendimiento, ya que el sistema operativo debe asignar recursos para un nuevo hilo. Por ello, no es recomendable crear cientos de workers para tareas triviales. Se deben usar de forma estratégica para tareas que realmente lo justifiquen por su carga computacional.

¿El método de los iframes es una alternativa viable?

En el pasado, se experimentó con el uso de múltiples iframes para simular hilos, partiendo de la base de que algunos navegadores podrían asignarles procesos diferentes. Sin embargo, este comportamiento no es estándar ni garantizado. La mayoría de los navegadores modernos ejecutan los iframes del mismo dominio en el mismo hilo que la página principal. Por lo tanto, no es un método fiable y ha sido completamente superado por los Web Workers.

Si quieres conocer otros artículos parecidos a Paralelismo en JavaScript: Guía Completa puedes visitar la categoría Juegos.

Subir