<!-- Canonical: https://lucioduran.com/es/blog/wasi-threads-shared-memory-parallel-wasm
Language: es
Content-SHA256: c93941b9892d2bf549747ddb6981410ab7cf53ab1128a561d376da00799f4bec
-->

WebAssembly

# WASI Threads: El Paralelismo con Memoria Compartida Llega a WebAssembly

2025-10-314 min lectura

Lucio Durán
Engineering Manager & AI Solutions Architect

También disponible en: [English](<https://lucioduran.com/blog/wasi-threads-shared-memory-parallel-wasm>)

## El modelo de threading

WASM threads usa un modelo de memoria compartida similar a pthreads. Múltiples threads comparten la misma memoria lineal. La sincronización pasa a través de operaciones atómicas sobre esa memoria.

```c
// Compilar con: emcc -pthread -s SHARED_MEMORY=1 -s PTHREAD_POOL_SIZE=8
#include <pthread.h>
#include <stdatomic.h>

#define WIDTH 800
#define HEIGHT 600
#define NUM_SAMPLES 64

static uint32_t framebuffer[WIDTH * HEIGHT];
static atomic_int next_row;
static atomic_int completed_rows;

void* render_thread(void* arg) {
 while (1) {
 int row = atomic_fetch_add(&next_row, 1);
 if (row >= HEIGHT) break;

 for (int x = 0; x < WIDTH; x++) {
 float r = 0, g = 0, b = 0;
 for (int s = 0; s < NUM_SAMPLES; s++) {
 Ray ray = camera_ray(x, row, s);
 Vec3 color = trace(ray, scene, 0);
 r += color.x; g += color.y; b += color.z;
 }
 r /= NUM_SAMPLES; g /= NUM_SAMPLES; b /= NUM_SAMPLES;
 framebuffer[row * WIDTH + x] = pack_rgba(r, g, b, 1.0f);
 }
 atomic_fetch_add(&completed_rows, 1);
 }
 return NULL;
}
```

## Integración con el browser: Workers + SharedArrayBuffer

En el browser, cada thread de WASM corre en un Web Worker:

```javascript
const memory = new WebAssembly.Memory({
 initial: 256, maximum: 1024, shared: true
});

const numThreads = navigator.hardwareConcurrency || 4;

for (let i = 0; i < numThreads; i++) {
 const worker = new Worker('worker.js');
 worker.postMessage({
 type: 'init', wasmBytes, memory,
 threadId: i, totalThreads: numThreads
 });
}

// Leer el framebuffer y renderizar al canvas
const canvas = document.getElementById('canvas');
const ctx = canvas.getContext('2d');
const imageData = ctx.createImageData(800, 600);

function renderFrame() {
 const pixels = new Uint8ClampedArray(memory.buffer, 0, 800 * 600 * 4);
 imageData.data.set(pixels);
 ctx.putImageData(imageData, 0, 0);
 requestAnimationFrame(renderFrame);
}
renderFrame();
```

El requisito crítico: tu página debe configurar headers de seguridad para que ` SharedArrayBuffer ` funcione:

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

Sin estos headers, ` SharedArrayBuffer ` se deshabilita y tu memoria compartida no va a funcionar. Este es el gotcha de deploy más común.

## Performance: los números reales

Los benchmarks del ray tracer revelan resultados significativos. La escena es una Cornell box con 4 esferas, 64 samples por pixel, 800x600:

```
C nativo (gcc -O3, 8 threads): 1.240ms
WASM threads (Chrome, 8 workers): 1.890ms (1.52x nativo)
WASM threads (Firefox, 8 workers): 2.100ms (1.69x nativo)
WASM single-thread (Chrome): 9.450ms (7.62x nativo)
JavaScript (optimizado a mano, 8 workers): 14.200ms (11.45x nativo)
```

WASM threads logra 52-66% del rendimiento nativo. El scaling es casi lineal hasta el conteo de cores físicos:

```
1 thread: 9.450ms
2 threads: 4.780ms (1.98x speedup)
4 threads: 2.410ms (3.92x speedup)
8 threads: 1.890ms (5.00x speedup)
```

## WASI threads fuera del browser

La propuesta ` wasi-threads ` trae el mismo modelo a runtimes WASM del lado servidor:

```bash
clang --target=wasm32-wasi-threads \
 -pthread -matomics -mbulk-memory \
 -Wl,--import-memory,--shared-memory,--max-memory=67108864 \
 -o ray_tracer.wasm ray_tracer.c

wasmtime run --wasi threads ray_tracer.wasm
```

En Rust, el target ` wasm32-wasi-threads ` es experimental pero funcional:

```rust
use std::sync::atomic::{AtomicI32, Ordering};
use std::sync::Arc;

fn main() {
 let counter = Arc::new(AtomicI32::new(0));
 let mut handles = vec![];

 for _ in 0..8 {
 let counter = Arc::clone(&counter);
 let handle = std::thread::spawn(move || {
 for _ in 0..1000 {
 counter.fetch_add(1, Ordering::Relaxed);
 }
 });
 handles.push(handle);
 }

 for handle in handles {
 handle.join().unwrap();
 }
 assert_eq!(counter.load(Ordering::SeqCst), 8000);
}
```

## Primitivas de sincronización

```c
typedef struct { _Atomic int locked; } WasmMutex;

void wasm_mutex_lock(WasmMutex* m) {
 while (1) {
 int expected = 0;
 if (atomic_compare_exchange_weak(&m->locked, &expected, 1)) return;
 __builtin_wasm_memory_atomic_wait32(&m->locked, 1, -1);
 }
}

void wasm_mutex_unlock(WasmMutex* m) {
 atomic_store(&m->locked, 0);
 __builtin_wasm_memory_atomic_notify(&m->locked, 1);
}
```

El par ` wait32/notify ` es crucial. Sin ellos, usarías un spin-lock que quema ciclos de CPU. ` wait32 ` le dice al runtime "dormí este thread hasta que el valor en esta dirección de memoria cambie."

## Dónde importa esto

WASM threads desbloquean workloads que antes eran impracticables en el browser:

- **Procesamiento de imagen/video**: Operaciones paralelas de pixels
- **Computación científica**: Simulaciones Monte Carlo, operaciones de matrices
- **Motores de juegos**: Simulación de física en threads separados
- **Compresión**: Compresión de bloques paralela (zstd, brotli)
- **Inferencia de ML**: Multiplicación de matrices paralela para modelos chicos

WASM threads no son un reemplazo completo del threading nativo. Son una aproximación suficientemente buena que trae paralelismo real a ambientes sandboxeados. Para los workloads que se benefician, y el speedup de 5x del ray tracer con 8 threads prueba que existen, eso alcanza para cambiar lo que es posible.

## Preguntas frecuentes

### ¿Qué son los WASI threads?

WASI threads es una propuesta que agrega creación de threads y soporte de shared memory al WebAssembly System Interface. Permite que módulos WASM creen threads que comparten la misma memoria lineal, habilitando computación paralela real.

### ¿Puedo usar WASM threads en el browser?

Sí, via SharedArrayBuffer y Web Workers. Cada worker carga el mismo módulo WASM con memoria compartida. Los headers COOP y COEP deben estar seteados.

### ¿Cómo se compara el rendimiento de WASM threads con nativo?

En workloads compute-bound, WASM threads logran 60-80% del rendimiento nativo de threads. El overhead viene del bounds checking en acceso a memoria compartida y el costo de creación de workers del browser.

## Tips

1. **Tip:** Utilizar ` memory.atomic.wait32 ` y ` memory.atomic.notify ` para sincronización de threads en vez de spin-locks. Los spin-locks queman ciclos de CPU; wait/notify cede al scheduler.
2. **Tip:** Siempre compilá con ` -pthread -matomics -mbulk-memory ` cuando apuntés a WASM threads desde C/C++. Si falta algún flag, el binario cae silenciosamente a comportamiento single-threaded.
3. **Tip:** Configurar los headers ` Cross-Origin-Opener-Policy: same-origin ` y ` Cross-Origin-Embedder-Policy: require-corp ` en tu página HTML. Sin estos, los browsers deshabilitan ` SharedArrayBuffer `.
4. **Tip:** Perfilar con ` performance.measureUserAgentSpecificMemory() ` para trackear uso de memoria por thread. Los profilers de heap estándar no distinguen memoria shared vs private en WASM.

- wasi
- wasm
- threads
- shared-memory
- atomics
- parallel-computing
- webassembly
- ray-tracer

[Volver al Blog](<https://lucioduran.com/es/blog>)
