1. Problem It Solves
Multiple threads may execute at the same time and touch shared memory. The threading library starts work, a mutex protects compound non-atomic operations, atomics provide indivisible operations, and the memory model defines when accesses form a data race.
Focus on the smallest useful form, its observable behavior, and its safety boundary.
2. Prerequisites
Days 1-6; functions, lambdas, RAII-style scope, and the difference between shared and local state.
3. Core Idea
Thread interleaving is nondeterministic. Every shared write needs a synchronization story: either the same mutex guards all accesses or an atomic operation supplies the required ordering.
Identify the objects and types, today's operation, and the printed result. This connects syntax to behavior.
4. Minimal Syntax
std::lock_guard<std::mutex> lock(m);
counter.fetch_add(1, std::memory_order_relaxed);5. How It Works
Two worker threads receive independent input values but update shared counters.
A lock guard serializes changes to the ordinary integer, while an atomic increment safely counts completed events.
Joining both threads establishes completion before main reads and prints the final totals.
6. Common Mistakes
Writing the same ordinary variable from multiple threads without synchronization is a data race and therefore undefined behavior.
Do not copy the pattern without checking which objects are shared, which synchronization operation covers each access, and when threads are joined. A program may compile while still having the wrong lifetime, ownership, invalidation, ordering, or performance behavior.
7. When to Use It
Use it when independent work can overlap and shared state has a clear, minimal synchronization design.
Avoid it when the work is tiny, inherently sequential, or synchronization cost and complexity exceed the benefit.
8. Simple Example
Two workers add fixed amounts to a protected total and increment an atomic event count. The default join order does not matter because the mutex and atomic remove conflicting unsynchronized accesses.
The .cpp file uses fixed data. Predict its output, compile it, then change one value and test the prediction.
Complete sample code
Source file
cpp14/07_thread_mutex_atomic_memory_model_review/main.cpp
#include <atomic>
#include <iostream>
#include <mutex>
#include <thread>
int main() {
std::mutex mutex;
int protected_total = 0;
std::atomic<int> events{0};
auto worker = [&](int amount) {
{
std::lock_guard<std::mutex> lock(mutex);
protected_total += amount;
}
events.fetch_add(1, std::memory_order_relaxed);
};
std::thread first(worker, 10);
std::thread second(worker, 20);
first.join();
second.join();
std::cout << "total: " << protected_total << "\n";
std::cout << "events: " << events.load() << "\n";
}
9. Key Takeaways
Correct concurrent code maps every shared access to a happens-before or mutual-exclusion rule.
Thread interleaving is nondeterministic. Every shared write needs a synchronization story: either the same mutex guards all accesses or an atomic operation supplies the required ordering.
The compiler or library follows a precise rule; verify which objects are shared, which synchronization operation covers each access, and when threads are joined.
Prefer the smallest form that communicates intent and measure costs when performance matters.
10. Self-Check Questions
Easy — What is the main purpose of Reviewing thread, mutex, atomic, and the C++ Memory Model?
Medium — Why is the final total always 30 even though either worker may run first?
Hard — Why is
memory_order_relaxedsufficient for the independent event count here but not automatically sufficient to publish unrelated data?