liveBook · Manning

Chapter 8: Chapter 8. Designing concurrent code

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

The introduction shifts from building thread-safe data structures to designing useful concurrent code, outlining the decisions and tradeoffs the chapter will examine.

After this chapter you will be able to

What it builds on

Section by section

Introduction

The introduction shifts from building thread-safe data structures to designing useful concurrent code, outlining the decisions and tradeoffs the chapter will examine.

Key terms: concurrent code, data structures, parallel algorithms, exception safety, scalability, how many threads, shared data, synchronize accesses, wait, clarity

8.1 Techniques for dividing work between threads

This section uses a house-building analogy to introduce choices about how many threads to use and whether to assign them general or specialized work.

Key terms: generalist threads, specialist threads, number of threads

8.1.1 Dividing data between threads before processing begins

This section explains how to parallelize element-wise work by dividing data among threads before processing, then combining results when necessary, and why some algorithms need a different approach.

Key terms: std::for_each, consecutive chunks, worker threads, reduction, accumulate, final serial step, Quicksort, recursive algorithms

8.1.2 Dividing data recursively

The section uses parallel Quicksort to show how recursively generated work can be shared among a bounded number of threads, along with the costs and limits of that design.

Key terms: Quicksort, pivot, partition, recursive calls, std::async(), std::thread::hardware_concurrency(), thread-safe stack, pending chunks, thread pool, try_sort_chunk

8.1.3 Dividing work by task type

This section explains how to divide concurrent work by task type, including specialist threads and pipelines, and when those designs help or hinder responsiveness and performance.

Key terms: dividing work by task type, separating concerns, single responsibility, main loop, user interface thread, background task, synchronization, shared data, waiting, communication between threads

8.2 Factors affecting the performance of concurrent code

This introduction frames multithreaded performance as a design concern and previews processor count and work assignment as factors to examine.

Key terms: concurrency, multiple processors, separate concerns, performance, processors, data elements, threads

8.2.1 How many processors?

This section explains how processor availability and hardware layout affect thread-count decisions and the performance of concurrent code.

Key terms: processing units, threads ready to run, oversubscription, number (and structure) of processors, target hardware, std::thread::hardware_concurrency(), std::async(), thread pools, size of the problem, number of processing units

8.2.2 Data contention and cache ping-pong

This section explains how writes to shared data make cache lines move between processors, creating contention that can erase the performance gains of additional threads.

Key terms: cache ping-pong, high contention, low contention, std::memory_order_relaxed, fetch_add, mutex, single-writer, multiple-reader mutex, processor stall, false sharing

8.2.3 False sharing

The section explains how cache-line granularity can make independent threads interfere with one another and how data layout can prevent it.

Key terms: cache line, cache ping-pong, false sharing, std::hardware_destructive_interference_size, <new>

8.2.4 How close is your data?

This section explains how keeping each thread’s data close together can reduce cache misses, especially when threads share cores or move between them.

Key terms: data proximity, cache line, cache miss, false sharing, task switching, oversubscription, cache lines, time slice, std::hardware_constructive_interference_size, <new>

8.2.5 Oversubscription and excessive task switching

The section explains when extra threads help, when too many runnable threads hurt performance, and how to respond to oversubscription.

Key terms: oversubscription, task switching, processors, cache proximity, data division, worker threads, task type, CPU-bound, division of work

8.3 Designing data structures for multithreaded performance

This section introduces how data layout and the assignment of data to threads affect multithreaded performance, beyond making a data structure safe for concurrent access.

Key terms: multithreaded performance, safe for concurrent access, data layout, contention, false sharing, data proximity

8.3.1 Dividing array elements for complex operations

Uses matrix multiplication to show how dividing array work among threads changes false sharing, cache use, and performance.

Key terms: row-major, false sharing, cache lines, adjacent rows, adjacent columns, rectangular blocks, source matrices, cache miss, data access patterns, block size

8.3.2 Data access patterns in other data structures

This section applies cache-locality and false-sharing considerations to trees and mutex-protected data, then shows how padding can help diagnose false sharing.

Key terms: data distribution, false sharing, std::hardware_destructive_interference_size, binary trees, subtree, tree nodes, mutex, read-modify-write atomic operation, cache line, padding

8.4 Additional considerations when designing for concurrency

This introduction adds exception safety and scalability to the concerns involved in designing concurrent code, and explains why exception safety comes first.

Key terms: scalable, throughput, linear, exception safety, invariants, race conditions

8.4.1 Exception safety in parallel algorithms

The section makes parallel accumulation exception-safe by identifying where exceptions can terminate a program, then showing how futures and reliable thread cleanup prevent that outcome.

Key terms: parallel_accumulate, std::thread, std::terminate, join, std::packaged_task, std::future, get(), accumulate_block, joinable(), thread_guard

8.4.2 Scalability and Amdahl’s law

Explains how serial work limits the speedup from additional processors and how keeping processors busy improves scalability.

Key terms: scalability, processors, dividing work between threads, Amdahl’s law, serial sections, parallel sections, serial fraction, speedup, CPU-bound, waiting

8.4.3 Hiding latency with multiple threads

This section explains how to keep processors useful when threads wait, while weighing extra threads against other ways to avoid idle time.

Key terms: blocked threads, physical processing units, spare CPU time, pipeline, I/O-bound, scanning threads, task switching, asynchronous I/O, outstanding chunks, responsiveness

8.4.4 Improving responsiveness with concurrency

The section shows how separating GUI event handling from a long-running task keeps an application responsive without mixing event-loop code into the task.

Key terms: event loop, get_event(), process(), dedicated GUI thread, event-handling thread, task thread, messages

8.5 Designing concurrent code in practice

The section introduces three parallel Standard Library algorithm implementations as practical examples of task-dependent concurrent design, beginning with std::for_each.

Key terms: parallel versions, C++ Standard Library, std::for_each, hardware concurrency

8.5.1 A parallel implementation of std::for_each

This section shows two ways to implement parallel std::for_each while accounting for unordered execution and exceptions from worker tasks.

Key terms: std::for_each, parallel implementation, std::thread::hardware_concurrency(), contiguous blocks, false sharing, std::packaged_task, std::future<void>, std::async, get(), recursive

8.5.2 A parallel implementation of std::find

The section builds two parallel versions of std::find that stop unnecessary searching, handle results and exceptions, and accept that parallel search may return a later match.

Key terms: std::find, done_flag, atomic variable, early, std::promise, std::future, join, exception, std::async, std::ref

8.5.3 A parallel implementation of std::partial_sum

This section implements parallel running totals in two ways and explains how dependencies, processor count, synchronization, and exceptions affect the designs.

Key terms: std::partial_sum, forward propagation, incremental pairwise algorithm, process_chunk, std::promise, std::future, std::for_each, barrier, wait(), done_waiting()

8.6 Summary

The chapter summary connects ways to divide work with performance and correctness concerns, then introduces thread pools as the next topic.

Where this is used later

Concepts explained in this chapter

← Chapter 7: Chapter 7. Designing lock-free concurrent data structures Chapter 9: Chapter 9. Advanced thread management →

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.