liveBook · Manning

Chapter 7: Chapter 7. Designing lock-free concurrent data structures

What you will learn in chapter 7 of C++ Concurrency in Action, Second Edition

The introduction motivates lock-free data structures, identifies atomic memory ordering as their foundation, and previews the chapter’s definitions, examples, memory-management techniques, and guidelines.

After this chapter you will be able to

What it builds on

Section by section

Introduction

The introduction motivates lock-free data structures, identifies atomic memory ordering as their foundation, and previews the chapter’s definitions, examples, memory-management techniques, and guidelines.

Key terms: mutexes, deadlock, granularity of locking, lock-free data structure, atomic operations, memory-ordering properties, lock-free data structures, managing memory

7.1 Definitions and consequences

This section distinguishes blocking from nonblocking data structures and explains what happens when a thread makes a blocking call.

Key terms: blocking data structures and algorithms, blocking calls, nonblocking, lock-free, blocked thread, time slices, unlocking a mutex, notifying a condition variable, making a future ready

7.1.1 Types of nonblocking data structures

The section distinguishes merely avoiding blocking calls from guaranteeing progress, using a spin lock to introduce obstruction-free, lock-free, and wait-free data structures.

Key terms: std::atomic_flag, spin lock, test_and_set(), nonblocking, lock-free, obstruction-free, wait-free, bounded number of steps

7.1.2 Lock-free data structures

This section defines lock-free progress, explains why compare/exchange retry loops can qualify, and distinguishes lock-free from wait-free behavior.

Key terms: lock-free, concurrently, suspended, compare/exchange, retry, spin lock, starvation, wait-free

7.1.3 Wait-free data structures

This section defines wait-freedom, explains why retry-based lock-free operations do not qualify, and notes the difficulty of achieving the stronger guarantee.

Key terms: wait-free data structure, lock-free data structure, compare_exchange_weak, compare_exchange_strong, single pass, bounded number of steps, wait-free data structures

7.1.4 The pros and cons of lock-free data structures

This section weighs the progress and robustness benefits of lock-free data structures against their correctness challenges, livelock risk, and possible performance costs.

Key terms: lock-free, wait-free, mutex lock, maximum concurrency, invariants, atomic operations, data race, ordering constraints, deadlock, live lock

7.2 Examples of lock-free data structures

The section introduces lock-free data-structure examples, beginning with a stack, and explains how memory ordering and platform behavior affect their design.

Key terms: atomic operations, memory-ordering guarantees, memory_order_seq_cst, memory_order_acquire, memory_order_release, memory_order_relaxed, std::atomic_flag, locks internal to the C++ Standard Library implementation, requirements, profile

7.2.1 Writing a thread-safe stack without locks

This section builds a lock-free stack with atomic compare/exchange, then shows why safe popping, exception safety, and node reclamation require more than an atomic head pointer.

Key terms: head, next, compare_exchange_weak(), push(), pop(), compare/exchange, dangling pointer, nullptr, std::shared_ptr<>, exception safety

7.2.2 Stopping those pesky leaks: managing memory in lock-free data structures

This section shows how a lock-free stack can reclaim removed nodes safely, why a simple deferred-deletion scheme works only when pop() has quiet periods, and why heavier use requires another approach.

Key terms: pop(), push(), node, reclaim, threads_in_pop, to_be_deleted, try_reclaim(), swap(), atomic exchange, compare_exchange_weak

7.2.3 Detecting nodes that can’t be reclaimed using hazard pointers

This section shows how hazard pointers let a lock-free stack defer deletion of nodes that other threads may still access, then examines the cost of a simple implementation and ways to reduce it.

Key terms: hazard pointers, outstanding hazard pointers, reclaim_later(), old_head, head, compare_exchange_strong(), hazard pointer, get_hazard_pointer_for_current_thread(), hp_owner, thread_local

7.2.4 Detecting nodes in use with reference counting

This section shows how reference counting can make node reclamation safe in a lock-free stack, from shared-pointer options to a manual split-count implementation.

Key terms: std::shared_ptr<>, std::atomic_is_lock_free, std::experimental::atomic_shared_ptr<T>, internal count, external count, counted_node_ptr, std::atomic<counted_node_ptr>, compare_exchange_weak(), double-word-compare-and-swap, compare_exchange_strong()

7.2.5 Applying the memory model to the lock-free stack

The section derives the memory orderings needed for a reference-counted lock-free stack by tracing publication, concurrent updates, and safe node deletion.

Key terms: happens-before, memory ordering, std::memory_order_relaxed, head, counted_node_ptr, compare_exchange_weak, increase_head_count, compare_exchange_strong, std::memory_order_release, ptr->data

7.2.6 Writing a thread-safe queue without locks

This section develops a lock-free queue from a single-producer, single-consumer design, addressing concurrent updates, safe node reclamation, and stalled pushes.

Key terms: head, tail, dummy node, single-producer, single-consumer (SPSC), happens-before, compare_exchange_strong(), std::atomic<T*>, increase_external_count(), internal_count, external_counters

7.3 Guidelines for writing lock-free data structures

The section introduces additional design guidelines drawn from the chapter’s examples because lock-free data structures are difficult to get right.

Key terms: lock-free data structures, concurrent data structures, guidelines

7.3.1 Guideline: use std::memory_order_seq_cst for prototyping

The section recommends starting lock-free data structures with sequentially consistent atomic operations, then considering weaker memory orderings only after the design works and its complete code can be examined.

Key terms: std::memory_order_seq_cst, total order, memory-ordering constraints, memory orderings, thread visibilities, ordering guarantees, algorithm checker

7.3.2 Guideline: use a lock-free memory reclamation scheme

This section explains how lock-free data structures can reclaim memory safely and why reusing nodes introduces a different hazard.

Key terms: lock-free memory reclamation, pending deletion, hazard pointers, reference counting, garbage collector, recycle nodes, ABA problem

7.3.3 Guideline: watch out for the ABA problem

This section explains how a compare/exchange operation can be fooled when a value changes away and back, and how a counter can detect that change.

Key terms: ABA problem, compare/exchange, free lists, ABA counter

7.3.4 Guideline: identify busy-wait loops and help the other thread

A lock-free queue should let a thread waiting on another operation help finish it instead of wasting CPU time in a busy-wait loop.

Key terms: busy-wait loop, blocking operation, atomic variable, compare/exchange operations

7.4 Summary

The section recaps the correctness and progress challenges of lock-free structures, then explains why their scalability and encapsulated synchronization matter to concurrent code.

Where this is used later

Concepts explained in this chapter

← Chapter 6: Chapter 6. Designing lock-based concurrent data structures Chapter 8: Chapter 8. Designing concurrent code →

These guides are generated from the text of Manning books by Manning's knowledge graph and link to the sections they draw on. The full books are available on liveBook and at manning.com.