This page demonstrates a step-by-step SP8D SPSC (Single-Producer, Single-Consumer) example for JavaScript and Node.js. Learn how to implement ultra-low-latency, lock-free channels for high-performance messaging in your apps.
SP8D SPSC Example: Single-Producer Single-Consumer in JavaScript & Node.js
The classic “lock-free queue” pattern—one producer, one consumer, highest throughput, zero overhead. SPSC is the fastest possible channel mode: no locks, no contention, and cache-friendly.
SPSC (Single-Producer, Single-Consumer) channels are the gold standard for ultra-low-latency, lock-free communication between exactly two threads or tasks. Use this pattern when you need maximum throughput and absolute ordering, with zero contention or ambiguity.
Minimal SPSC Example (Node.js or Browser)
This example walks you through the simplest SPSC channel: one producer, one consumer, zero contention. Perfect for ultra-low-latency pipelines or thread handoff. The diagram below shows the data flow.
Diagram: Producer uses send()
to move data to
the channel. Consumer uses recv()
to receive it. This direct
handoff is what enables SPSC’s ultra-low latency and simplicity.
import { createChannel } from "@sp8d/core";
// Create an SPSC channel with 4 slots, each slot holding up to 16 bytes.
const { channel } = createChannel({ slots: 4, slotSize: 16, mode: "SPSC" });
// Producer sends messages
for (let i = 0; i < 8; ++i) {
// Wait if channel is full (backpressure)
while (!channel.send(new Uint8Array([i, i * 10]))) {
// Buffer is full, so producer waits and retries (busy-wait)
}
console.log("Sent:", i);
}
// Consumer receives messages
let received: Uint8Array | null;
while ((received = channel.recv()) !== null) {
console.log("Received:", received);
}
The busy-wait loop (while (!send)
) is for demonstration only. In
production, use sendAsync()
or a backpressure-aware event loop to
avoid wasting CPU cycles.
Async/Real-World Example
For real-world, non-blocking usage, use sendAsync()
and the async
iterator as shown below.
// Producer (async)
for (let i = 0; i < 8; ++i) {
await channel.sendAsync(new Uint8Array([i, i * 10]));
console.log("Sent:", i);
}
// Consumer (async)
for await (const received of channel) {
console.log("Received:", received);
}
What’s happening?
- Producer: Calls
send()
in a loop—respects backpressure if the buffer is full. - Consumer: Calls
recv()
repeatedly—gets every value, in order, with no races or drops. - SPSC Mode Guarantee: Guarantees simple “ping-pong” correctness: no overwrites, slot loss, or ambiguity.
Backpressure is critical for lossless, high-throughput systems. Notice the producer waits (
while (!send)
) if the ring is full. This ensures you never lose data—even under burst conditions.
The output below shows the expected send/receive order for this example. If you run producer and consumer in separate threads, the interleaving may differ, but all messages will be delivered in order.
Sent: 0
Sent: 1
Sent: 2
Sent: 3
Received: Uint8Array([0, 0])
Received: Uint8Array([1, 10])
Received: Uint8Array([2, 20])
Received: Uint8Array([3, 30])
Sent: 4
Sent: 5
Sent: 6
Sent: 7
Received: Uint8Array([4, 40])
Received: Uint8Array([5, 50])
Received: Uint8Array([6, 60])
Received: Uint8Array([7, 70])
send()
returnsfalse
when full; always check for backpressure.recv()
returnsnull
when channel is empty.For browser use, producer and consumer may be in separate threads—see browser recipes.
For async use, see
recvAsync()
or the async iterator.If you see dropped messages, check your slot count and backpressure handling.
- For debugging, use diagnostics or enable verbose logging.
SPSC is the fastest mode, but only if you stick to the single-producer/single-consumer contract. If you break this contract, you may see data loss or protocol errors—use MPSC/MPMC for more complex patterns.
How does this work under the hood?
For a complete explanation of the slot state machine—including slot lifecycle, state transitions, atomic operations, and recovery—see the Slot State Machine documentation.