logo
Kurashizu Blogwhere ideas flow
  • Home
  • News
  • Blog
  • About
  • Home
  • News
  • Blog
  • About

© 2026 Kurashizu. All rights reserved.

Admin·New Post·Service Status
Powered by Cloudflare·02.11.26
← Back to news

Girls Just Wanna Have Fast MPMC Queues with Bounded Waiting

Original article
nahla.dev|39 points|1 comments|by EvgeniyZh|Jul 6, 2026

Girls Just Wanna Have Fast MPMC Queues with Bounded Waiting

By Nahla | March 12, 2026

Disclaimer: An earlier iteration of this article described this structure as wait-free. This was inaccurate. To be truly wait-free, the failure or suspension of any single thread must not impede the progress of any other thread. While I have updated the section regarding wait bounds, some legacy references to "wait-free" may remain to avoid a total rewrite of the original text.


Introduction

Following my previous post, I embarked on a project to create a data structure that allows multiple threads to share a mutable buffer. This journey led me down a rabbit hole of lock-free programming, eventually sparking a desire to implement a wait-free bounded-waiting queue.

I'm quite pleased with the resulting performance and felt it was worth documenting. After the "novella" length of my last entry, I wanted this piece to be short and sweet.

To be clear: I am not claiming this is a groundbreaking discovery. Rather, this is an exercise in testing my own intuition regarding lock-free and wait-free concurrency. Consequently, I've kept external citations to a minimum. I did, however, benchmark my implementation against other queues using max0x7ba's atomic_queue repository, which provided a streamlined way to gather data on popular implementations.

I welcome feedback from concurrency experts!


Design Theory

The core logic of this queue mimics a ticket lock system.

The Analogy

Imagine a system with:

  1. Two ticket dispensers (one for Producers, one for Consumers).
  2. NNN numbered boxes (indexed 000 to N−1N-1N−1).
  3. A display above each box showing a reservation number and a letter (P for Producer, C for Consumer).

Each ticket issued contains two pieces of information: the box number and the reservation number.

The Operational Flow:

  • Step 1: Grab a ticket from your respective dispenser.
  • Step 2: Locate the box matching the number on your ticket.
  • Step 3: Wait until the display above that box matches your reservation number and type.
  • Step 4: Perform the action (Producer →\rightarrow→ place item; Consumer →\rightarrow→ remove item).

The Invariant

The system ensures that for any specific box:

  • It begins in an empty state.
  • The first authorized access must be a Producer.
  • Access types must strictly alternate (e.g., P→C→P→CP \rightarrow C \rightarrow P \rightarrow CP→C→P→C).

This guarantee ensures producers never overwrite data and consumers never read from an empty box.


Reality: Implementation

To translate this theory into code, we utilize two AtomicUsize counters and two ring buffers.

ComponentAnalogyPurpose
CountersTicket DispensersTrack the next available reservation number.
Data BufferThe BoxesStore the actual elements being queued.
State BufferThe DisplaysTrack which reservation number currently owns the box.

The Data Structure

The implementation uses NonNull pointers to prevent accidental dereferencing (which could lead to Undefined Behavior) and manual allocation to avoid drop being called on values we no longer own.

// Cache padding is utilized to prevent "false sharing" across CPU cores
pub struct WFQueue<T, const N: usize> {
    // NonNull pointers avoid accidental dereferencing and UB
    data: NonNull<[CachePadded<T>; N]>, 
    state: NonNull<[CachePadded<AtomicTicket>; N]>,
    prod_reserve: CachePadded<AtomicUsize>,
    cons_reserve: CachePadded<AtomicUsize>,
}

Technical Specifications

Assuming a system where usize is 8 bytes (64 bits).

The AtomicTicket is a wrapper around an AtomicUsize. It uses a bit-packing strategy to store both the ticket type and the sequence number:

  1. The Most Significant Bit (MSB): Indicates the ticket type (Producer vs. Consumer).
  2. The Lower 63 Bits: Store the actual reservation number.

Implications of this layout:

  • Overflow: Reservation numbers wrap around at the 2632^{63}263 boundary rather than 2642^{64}264.
  • Generation: Numbers are generated via fetch_add(1, Ordering::...).

Indexing and Optimization

To find the correct buffer index, we calculate the reservation number modulo NNN: index=reservation_number(modN)\text{index} = \text{reservation\_number} \pmod Nindex=reservation_number(modN)

Since division and modulo operations are computationally expensive, we enforce a constraint: NNN must be a power of 2. This allows us to replace the modulo operation with a bitwise AND mask: index = reservation_number & (N - 1)

Queue Logic Diagram Figure 1: Conceptual mapping between the state buffer and data buffer.