PARALLEL.DAY

An Art-Deco Candy Editorial for Parallel Computing

Scroll to explore

The Editorial

Parallel Processing in the Browser

The modern web browser is a marvel of concurrent execution. Web Workers, Service Workers, and SharedArrayBuffer have transformed what was once a single-threaded environment into a parallel computing platform. Understanding these primitives is essential for building responsive, high-performance web applications that leverage multi-core processors.

Each worker thread operates in its own execution context, isolated from the main thread and other workers. Communication happens through structured cloning of messages, ensuring thread safety without the complexity of shared-memory synchronization primitives that plague traditional multi-threaded programming.

"Concurrency is not parallelism. Concurrency is about dealing with lots of things at once. Parallelism is about doing lots of things at once."

- Rob Pike

Message Passing Architecture

The actor model, popularized by Erlang and adapted for the web through the postMessage API, treats concurrent entities as independent actors that communicate exclusively through asynchronous messages. This eliminates shared state and the deadlocks that accompany it, trading the convenience of shared memory for the safety of isolation.

Transferable objects offer an optimization path: rather than copying data between threads, ownership is transferred. The sending context loses access while the receiving context gains it. ArrayBuffer, MessagePort, and OffscreenCanvas all support this zero-copy transfer mechanism.

Beyond Web Workers

SharedArrayBuffer and Atomics bring true shared-memory parallelism to JavaScript. Atomic operations provide the synchronization primitives necessary for safe concurrent access: compare-and-swap, load, store, and wait/notify operations that mirror the low-level constructs of systems programming languages.

The security implications of shared memory led browsers to disable SharedArrayBuffer after the Spectre vulnerability. Its return, gated behind Cross-Origin Isolation headers, demonstrates how the web platform balances capability with security, requiring explicit opt-in to powerful but potentially dangerous features.

Scheduling and Priority

The Scheduler API introduces priority-based task scheduling to the web. Tasks can be assigned user-blocking, user-visible, or background priority, allowing developers to express intent about work importance. The browser uses these hints to optimize main-thread responsiveness, ensuring high-priority interactions are never blocked by lower-priority computation.

Cooperative scheduling through requestIdleCallback and the newer scheduler.yield() provides natural breakpoints where long-running tasks can yield to the event loop, maintaining smooth scrolling and animation even during heavy computation. This pattern of voluntary preemption is a hallmark of well-designed concurrent systems.

"The best parallel programs are embarrassingly parallel: they require no communication between their concurrent components at all."

- A common wisdom in HPC

Interactive Tutorial

01

Create a Worker

Web Workers run scripts in background threads. Create one by passing a script URL to the Worker constructor.

JavaScript main.js
const worker = new Worker('worker.js');
worker.postMessage({ task: 'compute' });
02

Handle Messages

Workers communicate through message events. The onmessage handler processes incoming data from the worker thread.

JavaScript main.js
worker.onmessage = (event) => {
  const result = event.data;
  updateUI(result);
};
03

Transfer Ownership

Transferable objects move data between threads without copying, providing zero-overhead inter-thread communication.

JavaScript main.js
const buffer = new ArrayBuffer(1024);
worker.postMessage(buffer, [buffer]);
// buffer.byteLength === 0 (transferred!)
04

Shared Memory

SharedArrayBuffer enables true shared memory between threads, with Atomics providing synchronization primitives.

JavaScript main.js
const shared = new SharedArrayBuffer(256);
const view = new Int32Array(shared);
Atomics.store(view, 0, 42);

Patterns of Parallelism

Fork-Join

Split work into independent subtasks, execute in parallel, then join results. The foundation of data parallelism, from MapReduce to parallel streams.

Pipeline

Chain processing stages where each stage runs concurrently. Output of one stage flows into the next, like a digital assembly line.

Actor Model

Independent actors with private state communicate through asynchronous messages. No shared memory, no locks, no deadlocks. Pure isolation.

A B C

Core Concepts

Thread Safety

Ensuring data integrity when multiple threads access shared resources simultaneously. Achieved through locks, atomic operations, or elimination of shared state entirely.

Race Conditions

When program behavior depends on the relative timing of events. Two threads reading and writing the same variable can produce different results each run.

Work Stealing

Load-balancing strategy where idle processors steal work from busy ones. Each thread maintains a deque; when empty, it steals from the tail of another thread's deque.

Amdahl's Law

The speedup of a program is limited by its sequential fraction. If 10% of work is inherently sequential, maximum speedup is 10x regardless of processor count.