Introduction
postMessage() copies data by default—slow for large datasets. Transferable objects like ArrayBuffer can be transferred (zero-copy), and SharedArrayBuffer enables true shared memory between threads.
Key Concepts
Transferable Object: Data that can be transferred (not copied) between threads.
SharedArrayBuffer: Memory shared between main thread and workers.
Atomics: API for thread-safe operations on shared memory.
Deep Dive
Transferable Objects
javascript// Without transfer - data is COPIED (slow for large data) const buffer = new ArrayBuffer(100_000_000); // 100MB worker.postMessage(buffer); // Copied! 200MB total now // With transfer - data is MOVED (fast, zero-copy) worker.postMessage(buffer, [buffer]); buffer.byteLength; // 0 - it's been transferred!
SharedArrayBuffer
javascript// main.js const sharedBuffer = new SharedArrayBuffer(1024); const sharedArray = new Int32Array(sharedBuffer); worker.postMessage({ buffer: sharedBuffer }); // Both threads can read/write sharedArray sharedArray[0] = 42; // worker.js self.onmessage = (e) => { const sharedArray = new Int32Array(e.data.buffer); console.log(sharedArray[0]); // 42 sharedArray[0] = 100; // Main thread sees this! };
Atomics for Thread Safety
javascriptconst sharedArray = new Int32Array(sharedBuffer); // Atomic operations prevent race conditions Atomics.add(sharedArray, 0, 5); // Atomic increment Atomics.load(sharedArray, 0); // Atomic read Atomics.store(sharedArray, 0, 10); // Atomic write Atomics.compareExchange(sharedArray, 0, 10, 20); // CAS // Wait/notify for coordination Atomics.wait(sharedArray, 0, expectedValue); // Block until changed Atomics.notify(sharedArray, 0, 1); // Wake one waiting thread
Security Requirements
javascript// SharedArrayBuffer requires these headers: // Cross-Origin-Opener-Policy: same-origin // Cross-Origin-Embedder-Policy: require-corp // Check if available if (typeof SharedArrayBuffer !== 'undefined') { // Can use SharedArrayBuffer }
Atomics.waitAsync() (ES2024+)
Atomics.wait() blocks the calling thread — unusable on the main thread. Atomics.waitAsync() returns a promise instead:
javascript// Main thread — non-blocking wait const sharedArray = new Int32Array(sharedBuffer); const result = Atomics.waitAsync(sharedArray, 0, 0); if (result.async) { result.value.then(() => { console.log('Worker updated the value!'); console.log('New value:', Atomics.load(sharedArray, 0)); }); } else { console.log('Already changed:', result.value); } // Worker thread — notify main thread Atomics.store(sharedArray, 0, 42); Atomics.notify(sharedArray, 0);
Atomics.waitAsync() enables main-thread coordination with workers without blocking the event loop. The returned object has { async: true, value: Promise } for async waits or { async: false, value: 'not-equal'|'timed-out' } for immediate results.
Common Pitfalls
- Using transferred buffer after transfer: It's empty!
- Race conditions with SharedArrayBuffer: Use Atomics.
- Missing security headers: SharedArrayBuffer won't be available.
Summary
Transferable objects enable zero-copy data transfer. SharedArrayBuffer provides true shared memory. Use Atomics for thread-safe operations. Ensure proper security headers for SharedArrayBuffer.
Code Examples
// Without transfer - data is COPIED (slow for large data)
const buffer = new ArrayBuffer(100_000_000); // 100MB
worker.postMessage(buffer); // Copied! 200MB total now
// With transfer - data is MOVED (fast, zero-copy)
worker.postMessage(buffer, [buffer]);
buffer.byteLength; // 0 - it's been transferred!// main.js
const sharedBuffer = new SharedArrayBuffer(1024);
const sharedArray = new Int32Array(sharedBuffer);
worker.postMessage({ buffer: sharedBuffer });
// Both threads can read/write sharedArray
sharedArray[0] = 42;
// worker.js
self.onmessage = (e) => {
const sharedArray = new Int32Array(e.data.buffer);
console.log(sharedArray[0]); // 42
sharedArray[0] = 100; // Main thread sees this!
};