DEV Community

Rigal Patel
Rigal Patel

Posted on

1

High-Performance JavaScript Simplified: Web Workers, SharedArrayBuffer, and Atomics

JavaScript is a single-threaded language, meaning tasks execute one at a time on the main thread. While this design simplifies development, it can lead to performance bottlenecks for computationally heavy tasks. This blog explores how Web Workers, SharedArrayBuffer, and Atomics can enable multithreading in JavaScript to build high-performance applications.

Why Use Web Workers, SharedArrayBuffer, and Atomics?

Web Workers

Web Workers run JavaScript in background threads, preventing intensive tasks from blocking user interactions like scrolling or button clicks.

SharedArrayBuffer

SharedArrayBuffer allows memory to be shared between the main thread and workers without copying, enabling faster communication.

Atomics

Atomics ensure safe and synchronized access to shared memory, preventing race conditions and maintaining data consistency across threads.

Example: A Real-World Task with Web Workers and SharedArrayBuffer

Let’s implement a simple and real-world example: calculating the sum of a large array in parallel.

Step 1: Creating a Web Worker Script

Create a file named worker.js to handle partial sum calculations:

// worker.js
self.onmessage = function (event) {
    const { array, start, end } = event.data;
    let sum = 0;
    for (let i = start; i < end; i++) {
        sum += array[i];
    }
    self.postMessage(sum);
};
Enter fullscreen mode Exit fullscreen mode

Step 2: Setting Up the Main Thread

In the main script, divide the task among workers.

// main.js
const array = Array.from({ length: 1_000_000 }, () => Math.floor(Math.random() * 100));
const numWorkers = 4;
const chunkSize = Math.ceil(array.length / numWorkers);
const workers = [];
const results = [];
let completedWorkers = 0;

// Create a SharedArrayBuffer for the array
const sharedBuffer = new SharedArrayBuffer(Int32Array.BYTES_PER_ELEMENT * array.length);
const sharedArray = new Int32Array(sharedBuffer);
sharedArray.set(array);

// Initialize workers
for (let i = 0; i < numWorkers; i++) {
    const worker = new Worker('worker.js');
    workers.push(worker);

    const start = i * chunkSize;
    const end = Math.min(start + chunkSize, array.length);

    worker.postMessage({ array: sharedArray, start, end });

    worker.onmessage = function (event) {
        results[i] = event.data;
        completedWorkers++;

        if (completedWorkers === numWorkers) {
            const totalSum = results.reduce((acc, curr) => acc + curr, 0);
            console.log('Total Sum:', totalSum);
        }
    };
}

Enter fullscreen mode Exit fullscreen mode

Step 3: Using Atomics for Synchronization

Use Atomics to manage progress or ensure all threads are done before proceeding.

const progress = new Int32Array(sharedBuffer);
Atomics.add(progress, 0, 1); // Increment progress

if (Atomics.load(progress, 0) === numWorkers) {
    console.log('All workers completed their tasks.');
}
Enter fullscreen mode Exit fullscreen mode

Benefits of This Approach

  • Smooth User Experience: Offloads computation from the main thread.

  • Faster Communication: SharedArrayBuffer avoids data copying between threads.

  • Thread Safety: Atomics provide tools to handle synchronization effectively.

Real-World Use Cases

  • Real-Time Analytics: Process large datasets in parallel for faster insights.

  • Gaming Engines: Perform physics simulations in separate threads.

  • Media Processing: Encode or decode video streams without UI lag.

References

MDN Web Docs: Web Workers

MDN Web Docs: SharedArrayBuffer

MDN Web Docs: Atomics

Image of Timescale

Timescale – the developer's data platform for modern apps, built on PostgreSQL

Timescale Cloud is PostgreSQL optimized for speed, scale, and performance. Over 3 million IoT, AI, crypto, and dev tool apps are powered by Timescale. Try it free today! No credit card required.

Try free

Top comments (0)

nextjs tutorial video

Youtube Tutorial Series 📺

So you built a Next.js app, but you need a clear view of the entire operation flow to be able to identify performance bottlenecks before you launch. But how do you get started? Get the essentials on tracing for Next.js from @nikolovlazar in this video series 👀

Watch the Youtube series

👋 Kindness is contagious

Please leave a ❤️ or a friendly comment on this post if you found it helpful!

Okay