JavaScript

SharedArrayBuffer: Concurrencia profunda y memoria compartida entre Workers.

Contenido de la guía

⚙️ Arquitectura Web · Concurrencia y Memoria

Cuando mandar datos a un Worker ya no basta y necesitas memoria compartida real

Enviar mensajes entre hilos no siempre significa compartir información de verdad. Hoy en Tu Código Cotidiano exploramos qué cambia cuando varios Workers trabajan sobre el mismo bloque de memoria.

🧠 SharedArrayBuffer ⚡ Atomics 🧵 Web Workers 🔒 Cross-Origin Isolation

1. Cuando mandar datos a un Worker ya no basta

Mandar datos a un Web Worker suele darnos una falsa sensación de concurrencia total. Nos acostumbramos a postMessage() y terminamos pensando que el hilo principal y el worker están operando sobre la misma información al mismo tiempo. Pero en el modelo clásico eso no es lo que ocurre: normalmente el navegador está clonando los datos o transfiriendo su propiedad, de modo que el bloque deja de pertenecer al contexto original.

Aquí aparece la idea central de esta guía. SharedArrayBuffer no es “otro buffer más”, sino un cambio de paradigma: permite que dos o más hilos trabajen sobre el mismo bloque subyacente de memoria. Y justo por eso exige una arquitectura más seria. Cuando compartes memoria real, ya no basta con mandar mensajes: necesitas coordinación explícita, empiezan a importar las condiciones de carrera y entran en juego requisitos de seguridad que no existen en el paso de mensajes tradicional. SharedArrayBuffer abre la puerta a la memoria compartida; Atomics existe para que esa puerta no desemboque en caos.

SVG abstracto en fondo oscuro: al centro, una matriz naranja brillante simboliza la memoria compartida; a ambos lados, el hilo principal y un worker se conectan al mismo bloque, mostrando acceso concu
Figura 1: A diferencia del paso de mensajes habitual, SharedArrayBuffer permite que múltiples hilos se conecten y manipulen exactamente el mismo bloque de memoria en tiempo real, abriendo la puerta a la concurrencia real en la web.

2. Copiar, transferir y compartir no son lo mismo

Para entender el verdadero peso de SharedArrayBuffer, primero hay que desmontar la confusión más común al trabajar con Web Workers: creer que mandar datos a otro hilo equivale automáticamente a compartir memoria. En el modelo habitual de JavaScript, eso no es lo que ocurre. Cuando envías información con postMessage(), el navegador suele aplicar clonación estructurada (structured clone) o, en algunos casos, transferencia de propiedad. Ninguna de esas dos rutas implica que ambos contextos estén viendo exactamente el mismo bloque físico de memoria al mismo tiempo.

Ese matiz lo cambia todo, porque entre hilos existen tres modelos de intercambio radicalmente distintos. Parecen parecidos desde fuera, pero a nivel de memoria y concurrencia producen arquitecturas completamente diferentes:

  • Copiar (clonación): ambos lados reciben datos equivalentes, pero cada uno vive en su propio bloque de memoria. Si el Worker modifica su copia, el hilo principal no ve ese cambio, porque nunca existió memoria compartida: solo dos versiones independientes del mismo contenido.
  • Transferir (ownership): aquí no se duplica el bloque; se cambia su dueño. Si transfieres un ArrayBuffer estándar, el contexto emisor pierde el acceso y el receptor pasa a controlar ese bloque. Es una forma eficiente de mover datos pesados, pero sigue sin existir acceso simultáneo desde ambos lados.
  • Compartir (SharedArrayBuffer): este es el salto real de paradigma. El hilo principal y uno o varios Workers operan sobre el mismo almacenamiento subyacente. Ya no hay copias ni cesión exclusiva de propiedad: hay memoria compartida de verdad. Y precisamente por eso aparecen problemas nuevos —coordinación, sincronización y condiciones de carrera— que no existen en el modelo clásico de paso de mensajes.

Dicho de forma simple: copiar duplica, transferir desplaza y compartir expone el mismo bloque a varios hilos a la vez. Toda la dificultad —y toda la potencia— de SharedArrayBuffer nace de esa última diferencia.

Diagrama en tres partes: copiar duplica los datos, transferir mueve el bloque y vacía el origen, y compartir permite que dos hilos usen la misma memoria central.
Figura 2: Entender estos tres modelos es vital. Un SharedArrayBuffer no duplica la memoria ni cambia su dueño; la expone simultáneamente a múltiples contextos de ejecución.

3. Qué es realmente un SharedArrayBuffer

En términos estrictos, un SharedArrayBuffer es un bloque de memoria binaria cruda diseñado para ser visible desde varios hilos al mismo tiempo. Igual que ocurre con un ArrayBuffer normal, no trabajas directamente sobre el buffer “a mano”: necesitas crear una vista tipada, como Int32Array, Uint8Array o cualquier otro arreglo tipado que te permita leer y escribir bytes con significado.

La diferencia decisiva aparece cuando ese buffer cruza la frontera entre el hilo principal y un Worker. Con un SharedArrayBuffer, el navegador no clona los datos ni cede la propiedad exclusiva del bloque: ambos contextos terminan apuntando al mismo almacenamiento subyacente. Dicho de forma simple, no estás mandando una copia ni moviendo un recurso; estás exponiendo una misma región de memoria a más de un hilo al mismo tiempo.

Y justo ahí nace el verdadero problema de concurrencia. Compartir memoria no significa coordinarla. Si dos hilos leen y escriben los mismos índices sin una disciplina clara, el sistema deja de ser predecible y aparecen condiciones de carrera. SharedArrayBuffer te da el terreno común; pero para que ese terreno no se convierta en caos, hace falta una capa adicional de sincronización.

// 1. Reservamos memoria compartida para 100 enteros (400 bytes)
const sab = new SharedArrayBuffer(100 * Int32Array.BYTES_PER_ELEMENT);

// 2. Creamos una vista tipada sobre el mismo bloque compartido
const sharedArray = new Int32Array(sab);

// 3. Escribimos un valor en una posición concreta
// En un entorno concurrente, conviene hacerlo con Atomics
Atomics.store(sharedArray, 0, 42);

// 4. Compartimos el buffer con el Worker
// El Worker no recibe una copia: recibe acceso al mismo bloque subyacente
worker.postMessage(sab);

El ejemplo anterior ya deja ver la idea central: el dato no viaja como copia ni como recurso transferido, sino como acceso compartido al mismo bloque subyacente. Pero eso todavía no resuelve la parte más delicada del problema. Haber abierto una memoria común no significa que los hilos sepan convivir dentro de ella.

4. Atomics: por qué compartir memoria no basta

Ese control adicional lo aporta el objeto global Atomics. Si dos hilos leen y escriben sobre el mismo bloque sin coordinación, lo que ganaste en cercanía a la memoria lo puedes perder inmediatamente en coherencia. El objetivo de Atomics es garantizar que ciertas operaciones sean indivisibles: una lectura, una escritura o una actualización no quedan partidas en mitad del proceso ni expuestas a interferencias impredecibles de otro hilo.

Dicho de otro modo: SharedArrayBuffer abre la posibilidad de compartir memoria; Atomics la convierte en una arquitectura utilizable. Funciones como Atomics.store(), Atomics.load() o Atomics.add() existen para imponer orden sobre ese espacio compartido. Y si quieres subir un nivel más, operaciones como Atomics.wait() y Atomics.notify() te permiten coordinar hilos con patrones más serios —por ejemplo, productor-consumidor— sin caer en espera activa ni desperdiciar CPU.

La intuición correcta es esta: compartir memoria te da velocidad potencial; sincronizarla te da verdad. Sin esa segunda capa, los datos dejan de ser una base fiable y se convierten en un territorio donde dos hilos pueden pisarse mutuamente sin avisar.

SVG técnico en fondo oscuro: dos hilos, uno azul y otro verde, se conectan a una memoria compartida central mediante compuertas luminosas que simbolizan la coordinación atómica.
Figura 3: Compartir el bloque de memoria no es suficiente. El objeto Atomics actúa como un sistema de válvulas de seguridad, garantizando que el acceso simultáneo de múltiples hilos se coordine sin corromper los datos de la aplicación.

5. El requisito oculto: cross-origin isolation

Todo lo anterior suena a la promesa perfecta del alto rendimiento, hasta que llega el primer choque con la realidad: intentas crear un SharedArrayBuffer y el navegador no te deja. Eso no es un capricho ni un bug extraño; es una restricción de seguridad de la web moderna. Las APIs de memoria compartida y los temporizadores de alta precisión quedaron protegidos detrás de un entorno aislado porque podían amplificar ataques de canal lateral asociados a vulnerabilidades como Spectre.

Por eso, hoy SharedArrayBuffer no depende solo de tu código JavaScript: depende también de la arquitectura HTTP de tu aplicación. Para usar memoria compartida real, tu documento debe ejecutarse en un contexto seguro y, además, estar cross-origin isolated. En otras palabras, el navegador necesita la garantía de que tu página no va a mezclar sin control recursos de otros orígenes dentro del mismo entorno donde quieres habilitar concurrencia profunda y temporizadores más sensibles.

Ese aislamiento no se activa desde el script, sino desde el servidor. La combinación más común consiste en dos cabeceras: una que aísla tu documento del resto del contexto de navegación y otra que restringe qué recursos externos pueden cargarse o incrustarse si no se presentan de forma explícitamente autorizada. El precio de SharedArrayBuffer no es solo entender memoria compartida: también es aceptar un contrato de seguridad más estricto para toda la página.

# Opción más común para habilitar cross-origin isolation

Cross-Origin-Opener-Policy: same-origin
Cross-Origin-Embedder-Policy: require-corp

# Variante posible en COEP, según el caso:
# Cross-Origin-Embedder-Policy: credentialless

COOP separa tu documento en un grupo de contexto de navegación propio, mientras que COEP endurece la política de carga e incrustación de recursos cross-origin. Dicho de forma simple: el navegador te concede memoria compartida solo si tu aplicación acepta vivir dentro de una caja más aislada y más estricta. Antes de activarlo, conviene revisar qué scripts, imágenes, iframes o dependencias de terceros podrían romperse bajo estas reglas.

Estás viendo solo el 60% del contenido. Hazte Premium para acceder a la guía completa.

Comunidad

Comentarios y valoraciones

No hay comentarios aún. ¡Sé el primero en opinar!