Shared Data and Synchronization
Explore how to coordinate multiple threads safely in C++ by managing shared data with synchronization tools like std::mutex, std::lock_guard, and std::unique_lock. Understand data races, critical sections, and common pitfalls like deadlocks. Learn best practices for designing concurrent programs to maintain correct and efficient multithreaded behavior.
In the previous lesson, we learned how to launch threads to execute tasks concurrently. While creating threads is straightforward, the true challenge of concurrency lies in coordination. When multiple threads access and modify the same memory without proper synchronization, data can become corrupted, program invariants can be violated, and subtle bugs can emerge that are extremely difficult to reproduce and debug.
In this lesson, we'll focus on the mechanisms that allow threads to cooperate safely. We will learn how to coordinate access to shared data, prevent race conditions, and ensure that program state remains consistent, regardless of how many threads are executing concurrently.
The problem: Data races
A data race occurs when two or more threads access the same memory location concurrently, at least one of those accesses is a write, and there is no synchronization to coordinate them.
This problem arises because most C++ operations are not atomic. Even an expression as simple as counter++ is actually composed of multiple machine-level steps:
Read the current value from memory into a CPU register.
Modify the value in the register (increment it).
Write the updated value back to memory.
If two threads execute this sequence at the same time, they may both read the same initial value, for example, 5 increment it to 6, and then write 6 back to memory. Instead of the counter increasing by two, it increases by only one. One update is silently lost.
Example: An unsafe counter
Consider a flawed program in which ten threads each attempt to increment a shared counter 20,000 times. Intuitively, we would expect the final value to be 200,000. However, without proper synchronization, the result is unpredictable and almost always lower due to lost updates caused by data races.
This example ...