Skip to content

Node.js Worker Threads Data Sharing — Complete Guide to SharedArrayBuffer and Atomics

DodaTech Updated 2026-06-28 5 min read

In this tutorial, you will learn about Node.js Worker Threads Data Sharing. We cover key concepts, practical examples, and best practices to help you master this topic.

Node.js worker threads data sharing uses SharedArrayBuffer for zero-copy memory access, Atomics for thread-safe operations, and message passing for structured data communication.

What You'll Learn

By the end of this tutorial, you'll share memory between workers using SharedArrayBuffer, use Atomics for safe concurrent access, transfer ownership with transferable objects, and choose the right sharing pattern.

Why Data Sharing Matters

Message passing copies data between threads. For large datasets, copying causes significant overhead. SharedArrayBuffer eliminates copies but requires synchronization.

Real-World Use

A real-time analytics pipeline shares a circular buffer of recent events via SharedArrayBuffer. Multiple workers write new events while the main thread reads for display, using Atomics for coordination.

Data Sharing Path

flowchart LR
  A[Worker Threads] --> B[Data Sharing]
  B --> C[N-API Addons]
  C --> D[Performance]
  D --> E[Memory Leaks]
  B --> F{You Are Here}
  style F fill:#f90,color:#fff

SharedArrayBuffer Basics

SharedArrayBuffer is an ArrayBuffer accessible from multiple threads simultaneously.

const { Worker } = require("node:worker_threads");
const sharedBuffer = new SharedArrayBuffer(256);
const sharedArray = new Int32Array(sharedBuffer);
sharedArray[0] = 42;
const worker = new Worker(`
  const { parentPort } = require("worker_threads");
  parentPort.on("message", (buf) => {
    const arr = new Int32Array(buf);
    arr[1] = arr[0] * 2;
    parentPort.postMessage("done");
  });
`, { eval: true });
worker.postMessage(sharedBuffer);
worker.on("message", () => {
  console.log("Shared array:", sharedArray[0], sharedArray[1]);
});

Atomics for Synchronization

Atomics provides atomic operations on SharedArrayBuffer to prevent race conditions.

const { Worker } = require("node:worker_threads");
const buffer = new SharedArrayBuffer(4);
const counter = new Int32Array(buffer);
Atomics.store(counter, 0, 0);
const worker = new Worker(`
  const { parentPort } = require("worker_threads");
  parentPort.on("message", (buf) => {
    const arr = new Int32Array(buf);
    for (let i = 0; i < 100000; i++) {
      Atomics.add(arr, 0, 1);
    }
    parentPort.postMessage("done");
  });
`, { eval: true });
worker.postMessage(buffer);
for (let i = 0; i < 100000; i++) {
  Atomics.add(counter, 0, 1);
}
worker.on("message", () => {
  console.log("Final counter:", Atomics.load(counter, 0));
});

Message Passing for Complex Data

For complex data structures, use structured clone algorithm via postMessage.

const { Worker } = require("node:worker_threads");
const worker = new Worker(`
  const { parentPort } = require("worker_threads");
  parentPort.on("message", (data) => {
    const processed = data.map((item) => ({
      ...item,
      processed: true,
      timestamp: Date.now(),
    }));
    parentPort.postMessage(processed);
  });
`, { eval: true });
worker.postMessage([
  { id: 1, name: "Alice" },
  { id: 2, name: "Bob" },
]);
worker.on("message", (result) => {
  console.log("Processed:", result.length, "items");
});

Transferable Objects

Transfer ArrayBuffer ownership to avoid copying large buffers.

const { Worker, isMainThread, parentPort } = require("node:worker_threads");
function createWorker() {
  return new Worker(`
    const { parentPort } = require("worker_threads");
    parentPort.on("message", (buf) => {
      const view = new Uint8Array(buf);
      for (let i = 0; i < view.length; i++) view[i] = i % 256;
      parentPort.postMessage("done");
    });
  `, { eval: true });
}
const buffer = new ArrayBuffer(1024 * 1024 * 10);
const worker = createWorker();
worker.postMessage(buffer, [buffer]);
console.log("Buffer detached:", buffer.byteLength === 0);

Worker Communication Patterns

Choose the right pattern based on data size and access frequency.

const patterns = {
  sharedBuffer: "Use for large datasets, frequent reads/writes, zero-copy needed",
  transferable: "Use for one-time transfer of large buffers, ownership transfer",
  structuredClone: "Use for complex objects, infrequent communication, small data",
};
Object.entries(patterns).forEach(([name, desc]) => {
  console.log(`${name}: ${desc}`);
});

Common Mistakes

1. Race Conditions Without Atomics

Multiple threads writing to the same SharedArrayBuffer without Atomics causes data corruption.

2. Using the Buffer After Transfer

Transferred buffers become detached. Accessing them after transfer throws errors.

3. Overusing SharedArrayBuffer for Small Data

For small messages, structured clone overhead is negligible. Use message passing for simplicity.

4. Forgetting SharedArrayBuffer Requires Specific Headers

HTTP responses must include Cross-Origin-Opener-Policy and Cross-Origin-Embedder-Policy headers.

5. Not Using Atomics.wait for Synchronization

SharedArrayBuffer writes may not be visible to other threads without Atomics or synchronization.

Practice Questions

1. What is the advantage of SharedArrayBuffer over message passing?

Zero-copy access. Both threads read/write the same memory without Serialization.

2. What is Atomics.add used for?

Atomically adds a value to a SharedArrayBuffer element, preventing race conditions.

3. What happens to a transferred ArrayBuffer?

The sender's buffer becomes detached (length 0). Ownership moves to the receiver.

4. What headers are required for SharedArrayBuffer?

Cross-Opener-Policy: same-origin and Cross-Embedder-Policy: require-corp.

5. Challenge: Implement a shared counter using Atomics across multiple workers.

const buffer = new SharedArrayBuffer(4);
const counter = new Int32Array(buffer);
Atomics.store(counter, 0, 0);
const workers = Array.from({ length: 4 }, () => new Worker("./counter-worker.js"));
workers.forEach((w) => w.postMessage(buffer));
Promise.all(workers.map((w) => new Promise((r) => w.on("exit", r)))).then(() => {
  console.log("Final count:", Atomics.load(counter, 0));
});

FAQ

Is SharedArrayBuffer safe to use?

Yes, with proper Atomics synchronization. Without Atomics, concurrent access causes undefined behavior.

What is the maximum size of a SharedArrayBuffer?

Limited by available memory and the V8 heap. Typically up to 2GB on 64-bit systems.

Can I share objects directly between workers?

No. Objects must be cloned (structured clone) or shared via SharedArrayBuffer as bytes.

What is Atomics.wait and Atomics.notify?

Low-level synchronization primitives. wait pauses the thread until notify is called by another thread.

Are there any security concerns with SharedArrayBuffer?

SharedArrayBuffer requires specific HTTP headers for spectre vulnerability mitigation.

Mini Project: Shared Counter with Multiple Workers

Build a parallel counter that aggregates results via shared memory.

const { Worker } = require("node:worker_threads");
const buffer = new SharedArrayBuffer(8);
const data = new Int32Array(buffer);
Atomics.store(data, 0, 0);
Atomics.store(data, 1, 0);
const code = `
  const { parentPort } = require("worker_threads");
  parentPort.on("message", (buf) => {
    const arr = new Int32Array(buf);
    for (let i = 0; i < 500000; i++) {
      Atomics.add(arr, 0, 1);
      Atomics.add(arr, 1, i);
    }
    parentPort.postMessage("done");
  });
`;
const workers = Array.from({ length: 4 }, () => new Worker(code, { eval: true }));
workers.forEach((w) => w.postMessage(buffer));
Promise.all(workers.map((w) => new Promise((r) => w.on("exit", r)))).then(() => {
  console.log("Count:", Atomics.load(data, 0));
  console.log("Sum:", Atomics.load(data, 1));
});

What's Next

Node.js Worker Threads Node.js N-API Addons Node.js Performance

Built by the developers of DodaTech

Doda Browser, DodaZIP & Durga Antivirus Pro