38.4 SharedArrayBuffer and Atomics in TypeScript
Right, let’s talk about making your threads play nice with each other. You’ve got your Web Workers humming along, each in their own little isolated universe. That’s great for most tasks, but what if you need them to collaborate on a single, massive chunk of data? Passing that data back and forth with postMessage is like trying to share a single ice cream cone by mailing it to each other—it’s messy, inefficient, and by the time it gets to the third person, it’s just a sticky puddle of regret.
This is where SharedArrayBuffer and Atomics crash the party. They are the low-level, slightly dangerous power tools that let you create a block of raw memory that multiple workers (or the main thread) can read and write to directly. No copying. No messaging overhead. Just pure, unadulterated, shared memory concurrency. It’s incredibly powerful and, as you’ve probably guessed, a fantastic way to create subtle, mind-bending bugs if you don’t know what you’re doing.
The SharedArrayBuffer: Your Shared Memory Canvas
A SharedArrayBuffer is exactly what it sounds like: an array buffer whose backing memory is shared between threads. You create one just like a regular ArrayBuffer.
// In your main thread (or a worker)
const sharedBuffer = new SharedArrayBuffer(1024); // 1KB of shared memory
Now, here’s the first critical thing to understand: a SharedArrayBuffer is just raw bytes. It’s a blank slate. It has no idea what kind of data it’s holding. To make it useful, you need to create a “view” onto that memory. This is where typed arrays come in.
// Create a view that treats the memory as an array of 32-bit integers
const sharedArray = new Int32Array(sharedBuffer);
The magic happens when you pass this sharedBuffer (not the view!) to a worker via postMessage. On the other side, the worker can create its own view onto the exact same physical memory.
// Inside a Web Worker (worker.ts)
self.onmessage = (e: MessageEvent<SharedArrayBuffer>) => {
const sharedBufferFromMain = e.data;
// Create a view on the SAME memory the main thread is using
const myViewOfTheArray = new Int32Array(sharedBufferFromMain);
// Now any change made here...
myViewOfTheArray[0] = 42;
// ...is immediately visible in the main thread's view. No message passing!
};
This is the performance win. But it’s also the problem. What if both threads try to write to myViewOfTheArray[0] at the exact same nanosecond? You get a race condition. The final value becomes a lottery, depending on which thread’s write operation happened to complete last. This is where our bouncers, the Atomics object, come in.
Atomics: The Synchronization Bouncers
Atomics provides a set of static methods for performing atomic operations on the typed arrays you’ve created from a SharedArrayBuffer. “Atomic” means the operation is indivisible; it completes without interruption from any other thread. It’s the only safe way to read from or write to a shared memory location to avoid those torn reads and writes.
The most basic operations are load and store.
// Safely read a value from index 0
const currentValue = Atomics.load(sharedArray, 0);
// Safely write a value to index 0
Atomics.store(sharedArray, 0, newValue);
Why use these instead of sharedArray[0]? Because a simple index read/write in JavaScript is not guaranteed to be atomic. On some hardware architectures, a write might not be instantaneous, and another thread could read a partially updated, garbage value. Atomics.load and Atomics.store guarantee this doesn’t happen.
But the real magic is in operations like compareExchange (compare-and-swap) and add. These are the building blocks for locks and other synchronization primitives.
// A classic example: atomically incrementing a counter.
function atomicIncrement(array: Int32Array, index: number) {
let oldValue;
do {
oldValue = Atomics.load(array, index); // Read the current value
// Try to swap it for oldValue + 1. If someone else changed it in the meantime,
// it will fail and we loop to try again with the new value.
} while (
Atomics.compareExchange(array, index, oldValue, oldValue + 1) !== oldValue
);
}
This pattern is the core of non-blocking synchronization. You keep trying until you succeed without anyone else interfering.
The Crucial Security Headers and Gotchas
Here’s the part where the browser vendors rightfully got spooked. SharedArrayBuffer is a powerful feature that was temporarily yanked from the web after the Spectre and Meltdown vulnerabilities were discovered. The concern was that a malicious website could use precise timing attacks (measuring how long a SharedArrayBuffer read takes) to infer data from other websites or the OS itself.
To get it back, the browser makers put it behind two major security requirements that you, the developer, must enforce via HTTP headers:
Cross-Origin Isolation: Your page must be served with these headers:
Cross-Origin-Embedder-Policy: require-corp Cross-Origin-Opener-Policy: same-originThis essentially locks down your page, preventing it from interacting with cross-origin resources that don’t explicitly opt-in. This is non-negotiable. Without it,
SharedArrayBufferwill beundefined.The
postMessageGotcha: This one is a real head-scratcher. When you transfer aSharedArrayBufferto a worker, the object you send becomes neutered (unusable) in the sending thread. This is the same behavior as with a regularArrayBuffer. However, the views you created on that buffer? They are not transferred and remain active. This is a common pitfall. You’re meant to create new views on the receiving side from the transferred buffer.
When to Reach for This Hammer
This is not a tool for everyday use. The complexity and potential for bugs are significant. You should only consider SharedArrayBuffer when you have a performance-critical problem involving massive data sets that need real-time, fine-grained manipulation by multiple threads. Think physics simulations, image/video processing, scientific computing, or game engines.
For 95% of applications, the structured clone algorithm used by postMessage is more than fast enough and infinitely safer. Use that until your performance profiling tells you, unequivocally, that you have no other choice. When you do need it, though, it’s the only tool that can do the job. Just remember to wear your gloves and safety glasses—Atomics—at all times.