lock free queue

**Lock-Free Queues** are the **concurrent data structures that allow multiple threads to enqueue and dequeue elements simultaneously without using locks or blocking** — using atomic compare-and-swap (CAS) operations to resolve contention, providing guaranteed system-wide progress (at least one thread makes progress in any finite number of steps), and achieving significantly lower tail latency than lock-based queues under high contention. **Lock-Free vs. Wait-Free vs. Lock-Based** | Property | Lock-Based | Lock-Free | Wait-Free | |----------|-----------|-----------|----------| | Progress | Blocking (priority inversion) | System-wide (some thread progresses) | Per-thread (every thread progresses) | | Tail latency | Unbounded (lock holder preempted) | Bounded per-operation retries | Bounded per-thread | | Throughput | Good (low contention) | Great (moderate contention) | Lower (overhead of helping) | | Complexity | Simple | Complex | Very complex | **Michael-Scott Lock-Free Queue (MPMC)** - Classic lock-free FIFO queue using linked list + CAS. - Enqueue: 1. Allocate new node. 2. CAS tail→next from NULL to new node. (If fail, retry — another thread enqueued.) 3. CAS tail from old tail to new node. - Dequeue: 1. Read head→next. 2. CAS head from current to head→next. (If fail, retry.) 3. Return dequeued value. - **ABA problem**: Solved with tagged pointers (version counter) or hazard pointers. **Lock-Free Ring Buffer (SPSC)** - Single-Producer Single-Consumer: simplest and fastest lock-free queue. - Fixed-size circular buffer. Producer writes at `write_idx`, consumer reads at `read_idx`. - Only atomic load/store needed (no CAS) — because only one thread modifies each index. ```cpp struct SPSCQueue { std::atomic write_idx{0}; std::atomic read_idx{0}; T buffer[SIZE]; bool push(T val) { auto w = write_idx.load(relaxed); if ((w + 1) % SIZE == read_idx.load(acquire)) return false; // full buffer[w] = val; write_idx.store((w + 1) % SIZE, release); return true; } }; ``` **MPMC Ring Buffer** - Multiple producers, multiple consumers. - Each slot has a **sequence number** that tracks state (empty/full/in-progress). - CAS on sequence number to claim slot for write or read. - Higher throughput than linked-list queue (no allocation, cache-friendly). **Memory Reclamation (The Hard Part)** | Technique | How | Tradeoff | |-----------|-----|----------| | Hazard Pointers | Each thread publishes pointers it's using | Per-thread overhead, bounded memory | | RCU (Read-Copy-Update) | Defer freeing until all readers done | Fast reads, deferred reclamation | | Epoch-Based Reclamation | Threads advance through epochs | Simple, but unbounded if thread stalls | | Reference Counting | Atomic ref count per node | Simple, but contended counter | **Performance Characteristics** | Queue Type | Throughput (ops/sec) | Latency (p99) | |-----------|---------------------|---------------| | `std::mutex` + `std::queue` | ~10-50M | 1-100 μs | | SPSC ring buffer | ~100-500M | < 100 ns | | MPMC lock-free (Michael-Scott) | ~20-100M | 100-500 ns | | MPMC bounded (ring) | ~50-200M | 50-200 ns | Lock-free queues are **essential building blocks for high-performance concurrent systems** — from inter-thread communication in real-time systems to message passing in actor frameworks to I/O event dispatches, they provide the low-latency, non-blocking communication channels that modern parallel software depends on.

Go deeper with CFSGPT

Get AI-powered deep-dives, save terms, and run advanced simulations — free account.

Create Free Account