liveBook · Manning

Chapter 3: Chapter 3. Sharing data between threads

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

The chapter introduces the benefits and risks of sharing data between threads and previews ways to protect it.

After this chapter you will be able to

What it builds on

Section by section

Introduction

The chapter introduces the benefits and risks of sharing data between threads and previews ways to protect it.

Key terms: shared data, threads, updates, concurrency-related bugs, mutexes, alternative facilities for protecting shared data

3.1 Problems with sharing data between threads

The section explains how modifying shared data can temporarily break a data structure’s invariants, allowing another thread to observe an inconsistent state.

Key terms: shared data, read-only, invariants, doubly linked list, next pointer, previous pointer, broken invariant, partially removed, race condition

3.1.1 Race conditions

This section defines race conditions, distinguishes harmless ordering differences from broken invariants, and explains why problematic races can be difficult to reproduce.

Key terms: race condition, invariants, benign race conditions, problematic race conditions, distinct pieces of data, window of opportunity, debugger, data race, undefined behavior

3.1.2 Avoiding problematic race conditions

The section outlines three ways to prevent problematic race conditions and introduces mutexes as the first protection mechanism to examine.

Key terms: protection mechanism, intermediate states, invariants, mutex, lock-free programming, indivisible changes, memory model, software transactional memory, STM, transaction log

3.2 Protecting shared data with mutexes

This section introduces mutexes as a way to protect shared data from race conditions, while warning that correct locking requires careful code and interface design.

Key terms: mutex, mutual exclusion, lock, unlock, invariant, race conditions inherent in interfaces, deadlock, protecting either too much or too little data

3.2.1 Using mutexes in C++

The section shows how to use a mutex and RAII locking to protect shared data, and why the interface must not let callers bypass that protection.

Key terms: std::mutex, lock(), unlock(), std::lock_guard, RAII, add_to_list(), list_contains(), mutually exclusive, protected data, private members

3.2.2 Structuring code for protecting shared data

This section shows how pointers or references can let code access shared data outside the mutex meant to protect it.

Key terms: std::lock_guard, protected data, pointer, reference, user-supplied functions, scope of the lock

3.2.3 Spotting race conditions inherent in interfaces

The section shows why individually mutex-protected stack operations can still race, then redesigns the stack interface while accounting for exception safety and lock granularity.

Key terms: mutex, doubly linked list, whole data structure, delete operation, std::stack, empty(), size(), top(), undefined behavior, pop()

3.2.4 Deadlock: the problem and a solution

This section explains how deadlock arises when threads need multiple mutexes and how std::lock and C++17 std::scoped_lock help acquire them safely together.

Key terms: deadlock, mutex, lock order, std::lock, std::lock_guard, std::adopt_lock, std::mutex, all-or-nothing semantics, std::scoped_lock, RAII

3.2.5 Further guidelines for avoiding deadlock

The section explains how wait cycles cause deadlock and gives lock-ordering guidelines, including a runtime-checked mutex hierarchy, for preventing them.

Key terms: deadlock, wait cycle, join(), nested locks, user-supplied code, std::lock, fixed order, hand-over-hand locking, mutex per node, lock hierarchy

3.2.6 Flexible locking with std::unique_lock

This section explains how std::unique_lock supports deferred locking and tracks ownership, and when that flexibility is worth its cost.

Key terms: std::unique_lock, std::defer_lock, std::adopt_lock, std::lock(), owns_lock(), lock(), try_lock(), unlock(), destructor, std::lock_guard

3.2.7 Transferring mutex ownership between scopes

This section shows how std::unique_lock can transfer mutex ownership across scopes and release a lock before the lock object is destroyed.

Key terms: std::unique_lock, ownership, std::move(), lvalue, rvalue, moveable but not copyable, get_lock(), process_data(), gateway class, protected data

3.2.8 Locking at an appropriate granularity

This section explains how to reduce lock contention by protecting only the data and operations that need a lock, while preserving the meaning of those operations.

Key terms: lock granularity, fine-grained lock, coarse-grained lock, contention, std::unique_lock, unlock(), get_detail(), comparison operator, mutex, deadlock

3.3 Alternative facilities for protecting shared data

This section introduces initialization-only protection as an alternative to repeatedly locking a mutex for shared data that needs no explicit synchronization afterward.

Key terms: mutexes, shared data, initialization, explicit synchronization

3.3.1 Protecting shared data during initialization

This section explains how to initialize shared resources safely without making every later use wait on a mutex.

Key terms: lazy initialization, mutex, serialization, double-checked locking, data race, undefined behavior, std::once_flag, std::call_once, open_connection(), local variable declared with static

3.3.2 Protecting rarely updated data structures

This section shows how a reader-writer mutex protects a rarely updated DNS cache while allowing concurrent reads.

Key terms: reader-writer mutex, exclusive access, shared access, std::mutex, std::shared_mutex, std::shared_lock, std::lock_guard, std::unique_lock, find_entry(), update_or_add_entry()

3.3.3 Recursive locking

This section explains how recursive mutexes permit repeated locking by one thread, but recommends redesigning classes rather than using them to accommodate nested calls between locking member functions.

Key terms: std::mutex, std::recursive_mutex, lock(), unlock(), std::lock_guard<std::recursive_mutex>, std::unique_lock<std::recursive_mutex>, public member function, private member function, class invariants

3.4 Summary

This summary recaps how the chapter protects shared data, then explains why threads also need a way to wait efficiently for one another.

Where this is used later

Concepts explained in this chapter

← Chapter 2: Chapter 2. Managing threads Chapter 4: Chapter 4. Synchronizing concurrent operations →

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.