Overview

8 Wait-free synchronization

This chapter motivates wait-free synchronization as a practical path to predictable low latency when multiple threads must share data. It begins by noting that eliminating redundant work and exploiting CPU parallelism are not always sufficient; shared state still demands coordination. The focus is to understand traditional mutual exclusion, the atomic and memory-ordering foundations beneath all concurrency, and how to assemble these into wait-free data structures suitable for latency-sensitive, low-level components such as queues and request pipelines.

The text reviews mutual exclusion mechanisms—mutexes, read-write locks, and spinlocks—explaining critical sections and why contention, OS blocking, and spinning can inflate latency. It highlights core pathologies of lock-based designs: inefficiency under contention, priority inversion, convoying, and deadlocks, illustrated by simple counters and ABBA-style lock ordering pitfalls. From there, it introduces atomics as the hardware primitives underpinning synchronization (loads/stores, fetch-and-modify, and compare-and-exchange) and shows how even a spinlock can be built from them. Because modern CPUs and compilers reorder memory operations, the chapter details memory barriers/fences and their higher-level acquire/release/SeqCst/relaxed semantics, along with compiler fences, and demonstrates how pairing release on writes with acquire on corresponding reads preserves invariants without resorting to locks.

Wait-free synchronization is then defined and contrasted with lock-free and other progress conditions, emphasizing its benefits for low latency: bounded progress for every thread, scalability, deadlock immunity, and resilience to priority inversions. The chapter clarifies the hierarchy of progress guarantees (blocking, starvation-free, obstruction-free, lock-free, and the various wait-free forms) and warns that blocking memory allocation can undermine these guarantees. It introduces the notion of consensus numbers, noting that primitives like compare-and-swap are universal, and surveys wait-free queues, stacks, and linked lists, including the challenges of safe memory reclamation (reference counting, hazard pointers, epoch schemes, or GC). A practical single-producer, single-consumer bounded queue is constructed using atomic indices and acquire/release ordering to guarantee correctness without locks, demonstrating a dramatic drop in operation latency compared to a mutex-based design and illustrating how careful use of atomics and memory barriers yields predictable, low-latency concurrency.

Categories of memory barriers. Memory barriers are needed between two instructions that perform a load (read) or a store (write) to memory. In many programming languages today, memory barriers are abstracted as acquire and release barriers. Acquire barriers contain both load/load and load/store memory barriers release barriers contain both load/store and store/store memory barriers.
Example usage of acquire/release memory barriers to ensure ordering between writes to two variables, data and flag, where it’s critical that the write to data is visible when the write to flag becomes visible. In this example specifically, the flag indicates that some operation is done, where the result of the operation is store in data. If we allow the write to flag to be visible before the write to flag, the process can read invalid or stale data from data.
The relationship between different wait-free and lock-free progress conditions.

Summary

  • Wait-free synchronization is an alternative to traditional mutual exclusion for reducing latency in concurrent systems.
  • Mutual exclusion is traditionally implemented using mutexes, spinlocks, and read-write locks, but they have various problems for low latency apps, including inefficiency, priority inversion, convoying, and deadlocks.
  • Atomic operations and memory barriers are important in implementing wait-free concurrent data structures.
  • Wait-free has various progress conditions in concurrent programming, including blocking, starvation-free, obstruction-free, lock-free, and wait-free.
  • Wait-free data structures are presented as a solution for predictable low latency, multicore scalability, and fault tolerance in concurrent systems.
  • A practical example of building a wait-free single-producer, single-consumer (SPSC) queue is provided, demonstrating the use of atomics and memory barriers.
  • Wait-free can have significant performance improvements over traditional mutex-based implementation.

FAQ

What does “wait-free” mean, and how is it different from lock-free and obstruction-free?Wait-free synchronization guarantees every thread completes its operation in a finite number of steps, regardless of other threads. Lock-free only guarantees that some thread makes progress in finite time, which can yield high tail latency. Obstruction-free guarantees progress only if a thread runs in isolation (no contention). Wait-free comes in variants: - Population oblivious: strongest; same bounded steps for all threads - Wait-free bounded: each thread completes within a known upper bound (practical) - Wait-free (weakest): finite but potentially large completion time
Why can mutexes and other mutual exclusion primitives hurt latency?Under contention, locks introduce waiting, context switches, and wakeups, turning nanosecond overheads into microseconds. They also: - Cause convoying: threads queue behind a delayed lock holder - Create priority inversion: low-priority holders block high-priority threads (mitigated by priority inheritance) - Risk deadlocks (e.g., ABBA ordering) - Add atomic instruction and memory barrier overhead, and cross-node traffic on NUMA
When should I use a mutex, read-write lock, or spinlock?- Mutex: general-purpose exclusive access; use for potentially longer critical sections; easier semantics but can block - Read-write lock: many readers, rare writers; allows concurrent reads, exclusive writes; still blocks on write - Spinlock: very short critical sections where sleeping is too costly or impossible (e.g., kernel/interrupt context); avoid long holds to prevent CPU waste and latency spikes
What atomic operations underpin wait-free algorithms?Common atomic primitives: - Atomic loads/stores: indivisible reads/writes of shared variables - Fetch-and-modify (RMW): e.g., fetch_add/fetch_sub; x86-64 uses LOCKed instructions; ARM64 uses LDXR/STXR or LDADD - Compare-and-exchange (CAS): compare_exchange/compare_exchange_weak; x86-64 CMPXCHG; ARM64 LDXR/STXR or CAS These are the building blocks for higher-level wait-free data structures.
What are memory barriers and which orderings should I use?Memory barriers constrain reordering so multi-threaded code observes intended order: - Acquire: prevents later operations from moving before the barrier - Release: prevents earlier operations from moving after the barrier - Sequentially consistent (SeqCst): strongest, total order across threads - Relaxed: no ordering guarantees Higher-level mapping: acquire = LoadLoad+LoadStore; release = LoadStore+StoreStore; SeqCst = all; relaxed = none.
How do acquire/release pairings ensure data visibility in practice?Use release on the writer of the “flag” and acquire on the reader of the “flag.” Example pattern: - Writer: store data; store flag with Release - Reader: load flag with Acquire; if true, then subsequent loads see the data This pairing ensures the write to data becomes visible before the flag is observed true, preventing stale reads.
What’s the difference between CPU memory barriers and compiler barriers?CPU memory barriers (fence) stop the hardware from reordering memory operations across the barrier. Compiler barriers (compiler_fence in Rust) stop the compiler from reordering at optimization time but emit no CPU instruction. In typical high-level atomic code you rarely need compiler barriers; overusing them can harm performance.
How does a basic spinlock work and why can it be risky?A simple spinlock uses an atomic flag and spins until it can atomically set the flag (e.g., AtomicBool.swap or x86-64 xchg). It avoids OS blocking but: - Burns CPU while waiting - Must guard very short critical sections - Prolonged holds increase latency for others and waste cycles
What is a consensus number and why is CAS considered universal?The consensus number of a primitive is the maximum number of threads it can guarantee wait-free consensus for. Examples: - Atomic registers: 1 - Test-and-set, fetch-and-add, queues, stacks: 2 - Compare-and-swap (CAS) and queues with peek: infinite (universal) CAS is universal, so it can implement any object wait-free for any number of threads.
How does the single-producer, single-consumer (SPSC) wait-free queue work and how fast is it?Design: - Circular buffer with two atomic indices: back (producer) and front (consumer) - Producer: write element, then store back with Release - Consumer: load back with Acquire, read element, then store front with Release Acquire/Release ensures data is visible when indices are observed. Performance (example): lock-based pop ≈ 1.11 microseconds vs wait-free SPSC pop ≈ 73 nanoseconds, a large latency reduction.

pro $24.99 per month

  • access to all Manning books, MEAPs, liveVideos, liveProjects, and audiobooks!
  • choose one free eBook per month to keep
  • exclusive 50% discount on all purchases
  • renews monthly, pause or cancel renewal anytime

lite $19.99 per month

  • access to all Manning books, including MEAPs!

team

5, 10 or 20 seats+ for your team - learn more


choose your plan

team

monthly
annual
$49.99
$499.99
only $41.67 per month
  • five seats for your team
  • access to all Manning books, MEAPs, liveVideos, liveProjects, and audiobooks!
  • choose another free product every time you renew
  • choose twelve free products per year
  • exclusive 50% discount on all purchases
  • renews monthly, pause or cancel renewal anytime
  • renews annually, pause or cancel renewal anytime
  • Latency ebook for free
choose your plan

team

monthly
annual
$49.99
$499.99
only $41.67 per month
  • five seats for your team
  • access to all Manning books, MEAPs, liveVideos, liveProjects, and audiobooks!
  • choose another free product every time you renew
  • choose twelve free products per year
  • exclusive 50% discount on all purchases
  • renews monthly, pause or cancel renewal anytime
  • renews annually, pause or cancel renewal anytime
  • Latency ebook for free
choose your plan

team

monthly
annual
$49.99
$499.99
only $41.67 per month
  • five seats for your team
  • access to all Manning books, MEAPs, liveVideos, liveProjects, and audiobooks!
  • choose another free product every time you renew
  • choose twelve free products per year
  • exclusive 50% discount on all purchases
  • renews monthly, pause or cancel renewal anytime
  • renews annually, pause or cancel renewal anytime
  • Latency ebook for free