Preface xv
Part 1: Foundations of Parallel Programming and
Process Management
1
Parallel Programming Paradigms 3
Technical requirements 3
Getting to know classifications,
techniques, and models 4
Systems classification and techniques 4
Parallel programming models 6
Understanding various parallel
programming paradigms 9
Synchronous programming 10
Concurrency programming 10
Asynchronous programming 13
Parallel programming 13
Multithreading programming 14
Event-driven programming 14
Reactive programming 14
Dataflow programming 16
Exploring the metrics to assess
parallelism 16
Degree of parallelism 16
Amdahl’s law 17
Gustafson’s law 18
Summary 19
Further reading 19
2
Processes, Threads, and Services 21
Processes in Linux 22
Process life cycle – creation, execution, and
termination 22
Exploring IPC 23
Services and daemons in Linux 26
Threads 28
Thread life cycle 29
Thread scheduling 30
Synchronization primitives 31
Choosing the right synchronization primitive 31
Common problems when using
multiple threads 32
Strategies for effective thread
management 33
Summary 35
Further reading 35
Part 2: Advanced Thread Management and
Synchronization Techniques
3
How to Create and Manage Threads in C++ 39
Technical requirements 40
The thread library – an introduction 40
What are threads? Let’s do a recap 40
The C++ thread library 41
Thread operations 41
Thread creation 41
Synchronized stream writing 43
Sleeping the current thread 45
Identifying a thread 46
Passing arguments 47
Returning values 48
Moving threads 50
Waiting for a thread to finish 50
Joining threads – the jthread class 53
Yielding thread execution 55
Threads cancellation 57
Catching exceptions 61
Thread-local storage 63
Implementing a timer 64
Summary 67
Further reading 67
4
Thread Synchronization with Locks 69
Technical requirements 69
Understanding race conditions 70
Why do we need mutual exclusion? 72
C++ Standard Library mutual exclusion
implementation 74
Problems when using locks 80
Generic lock management 81
std::lock_guard 82
std::unique_lock 83
std::scoped_lock 84
std::shared_lock 84
Condition variables 85
Implementing a multithreaded
safe queue 87
Semaphores 95
Binary semaphores 95
Counting semaphores 96
Barriers and latches 101
std::latch 101
std::barrier 102
Performing a task only once 106
Summary 108
Further reading 108
5
Atomic Operations 109
Technical requirements 109
Introduction to atomic operations 110
Atomic operations versus non-atomic
operations – an example 110
When to use (and when not to use) atomic
operations 111
Non-blocking data structures 112
The C++ memory model 113
Memory access order 114
Enforcing ordering 117
Sequential consistency 118
Acquire-release ordering 121
Relaxed memory ordering 123
C++ Standard Library atomic types
and operations 124
C++ Standard Library atomic types 124
C++ Standard Library atomic operations 125
Example – simple spin-lock implemented
using the C++ atomic flag 126
An example of thread progress reporting 129
Example – simple statistics 130
Example – lazy one-time initialization 135
SPSC lock-free queue 138
Why do we use a power of 2 buffer size? 139
Buffer access synchronization 139
Pushing elements into the queue 140
Popping elements from the queue 141
Summary 143
Further reading 144
Part 3: Asynchronous Programming with
Promises, Futures, and Coroutines
6
Promises and Futures 147
Technical requirements 147
Exploring promises and futures 148
Promises 149
Futures 152
Shared futures 156
Packaged tasks 157
The benefits and drawbacks of
promises and futures 160
Benefits 161
Drawbacks 161
Examples of real-life scenarios
and solutions 161
Canceling asynchronous operations 162
Returning combined results 163
Chaining asynchronous operations 165
Thread-safe SPSC task queue 170
Summary 173
Further reading 174
7
The Async Function 175
Technical requirements 175
What is std::async? 176
Launching an asynchronous task 176
Passing values 177
Returning values 179
Launch policies 180
Handling exceptions 183
Exceptions when calling std::async 185
Async futures and performance 186
Limiting the number of threads 189
When not to use std::async 191
Practical examples 191
Parallel computation and aggregation 191
Asynchronous searches 193
Asynchronous matrix multiplication 197
Chain asynchronous operations 200
Asynchronous pipeline 201
Summary 206
Further reading 206
8
Asynchronous Programming Using Coroutines 207
Technical requirements 208
Coroutines 208
C++ coroutines 209
New keywords 210
Coroutines restrictions 211
Implementing basic coroutines 211
The simplest coroutine 211
A yielding coroutine 215
A waiting coroutine 220
Coroutine generators 224
Fibonacci sequence generator 224
Simple coroutine string parser 228
The parsing algorithm 228
The parsing coroutine 230
Coroutines and exceptions 233
Summary 234
Further reading 234
Part 4: Advanced Asynchronous Programming
with Boost Libraries
9
Asynchronous Programming Using Boost.Asio 237
Technical requirements 238
What is Boost.Asio? 238
I/O objects 239
I/O execution context objects 240
The event processing loop 245
Interacting with the OS 246
Synchronous operations 246
Asynchronous operations 247
The Reactor and Proactor design
patterns 249
Threading with Boost.Asio 250
Single-threaded approach 251
Threaded long-running tasks 252
Multiple I/O execution context objects,
one per thread 253
Multiple threads with a single I/O execution
context object 254
Parallelizing work done by one I/O execution
context 255
Managing objects’ lifetime 257
Implementing an echo server – an example 257
Transferring data using buffers 261
Scatter-gather operations 262
Stream buffers 263
Signal handling 265
Canceling operations 267
Serializing workload with strands 269
Coroutines 276
Summary 280
Further reading 280
10
Coroutines with Boost.Cobalt 283
Technical requirements 283
Introducing the Boost.Cobalt library 284
Eager and lazy coroutines 285
Boost.Cobalt coroutine types 285
Boost.Cobalt generators 286
Looking at a basic example 286
Boost.Cobalt simple generators 287
A Fibonacci sequence generator 290
Boost.Cobalt tasks and promises 292
Boost.Cobalt channels 296
Boost.Cobalt synchronization
functions 298
Summary 302
Further reading 302
Part 5: Debugging, Testing, and Performance
Optimization in Asynchronous Programming
11
Logging and Debugging Asynchronous Software 305
Technical requirements 305
How to use logging to spot bugs 306
How to select a third-party library 307
Some relevant logging libraries 307
Logging a deadlock – an example 309
How to debug asynchronous software 313
Some useful GDB commands 313
Debugging multithreaded programs 315
Debugging race conditions 318
Reverse debugging 320
Debugging coroutines 322
Summary 325
Further reading 325
12
Sanitizing and Testing Asynchronous Software 327
Technical requirements 327
Sanitizing code to analyze the
software and find potential issues 328
Compiler options 329
AddressSanitizer 331
LeakSanitizer 334
ThreadSanitizer 335
UndefinedBehaviorSanitizer 342
MemorySanitizer 342
Other sanitizers 343
Testing asynchronous code 344
Testing a simple asynchronous function 345
Limiting test durations by using timeouts 346
Testing callbacks 347
Testing event-driven software 348
Mocking external resources 349
Testing exceptions and failures 352
Testing multiple threads 353
Testing coroutines 355
Stress testing 359
Parallelizing tests 360
Summary 360
Further reading 360
13
Improving Asynchronous Software Performance 363
Technical requirements 363
Performance measurement tools 364
In-code profiling 364
Code micro-benchmarks 367
The Linux perf tool 374
False sharing 379
CPU memory cache 382
Cache coherency 383
SPSC lock-free queue 384
Summary 388
Further reading 388
Index 389
Other Books You May Enjoy 400
As hardware advancements continue to accelerate, bringing greater memory capacity and more CPU cores, software must evolve to adapt to efficiently use all available resources and reduce idle CPU cycles. In this book, two seasoned software engineers with about five decades of combined experience will teach you how to implement concurrent and asynchronous solutions in C++. You’ll gain a comprehensive understanding of parallel programming paradigms—covering concurrent, asynchronous, parallel, multithreading, reactive, and event-driven programming, as well as dataflows—and see how threads, processes, and services are related. Moving into the heart of concurrency, the authors will guide you in creating and managing threads and exploring C++’s thread-safety mechanisms, including mutual exclusion, atomic operations, semaphores, condition variables, latches, and barriers. With this solid foundation, you’ll focus on pure asynchronous programming, discovering futures, promises, the async function, and coroutines. The book takes you step by step through using Boost.Asio and Boost.Cobalt to develop network and low-level I/O solutions, proven performance and optimization techniques, and testing and debugging asynchronous software. By the end of this C++ book, you’ll be able to implement high-performance software using modern asynchronous C++ techniques.
This book is for developers who have some experience using C++, regardless of their professional field. If you want to improve your C++ skills and learn how to develop high-performance software using the latest modern C++ features, this book is for you.
Explore the different parallel paradigms and know when to apply them
Acquire deep knowledge of thread management and safety mechanisms
Understand asynchronous programming in C++, including coroutines
Leverage network asynchronous programming by using Boost.Asio and Boost.Cobalt
Add proven performance and optimization techniques to your toolbox
Find out how to test and debug asynchronous software