Concurrency
- C++ course
- Expert
- Lesson 45/47
- 6 min
Write parallel structure with concurrency and memory model.
Parallel sum with std::thread and std::atomic
For parallel work we launch two threads with std::thread. We hold the shared counter as std::atomic<int> so both threads can safely write at the same time. The total += i operation happens atomically and no data race occurs.
The threads sum the ranges 1-4 and 5-8. We wait for both to finish with t1.join() and t2.join(). The result is always 36; the total is fixed, only the write order varies. This deterministic output shows that atomic usage is safe.
Waiting with join
To wait for a thread to finish you call join(). t1.join() pauses the main flow and waits until t1 completes. When both threads finish, total holds the sum of all contributions.
The result is 1 + 2 + 3 + 4 + 5 + 6 + 7 + 8 = 36. Even if the write order differs, the total does not change. If you forget join, the program may end before the threads finish and std::terminate is called. That is why join is mandatory.
Practice Add the parallelSum method and call it
Take the previous lesson's solution (with the Engine class and Factorial template). Add static int parallelSum() under Engine's public: section. In its body define std::atomic<int> total{0};, launch two std::thread instances (ranges 1-4 and 5-8), join both and return total.load(). In main after the Factorial output add std::cout << std::endl << Engine::parallelSum();.
Starter code
#include <iostream>
#include <atomic>
#include <thread>
class Engine {
public:
static const int Version = 1;
// Add the parallelSum method here
};
template <int N>
struct Factorial {
static const int value = N * Factorial<N - 1>::value;
};
template <>
struct Factorial<0> {
static const int value = 1;
};
int main() {
std::cout << "Ready" << std::endl;
std::cout << Factorial<5>::value << std::endl;
// Print the parallelSum result
return 0;
}Sample solution
#include <iostream>
#include <atomic>
#include <thread>
class Engine {
public:
static const int Version = 1;
static int parallelSum() {
std::atomic<int> total{0};
std::thread t1([&]() { for (int i = 1; i <= 4; ++i) total += i; });
std::thread t2([&]() { for (int i = 5; i <= 8; ++i) total += i; });
t1.join();
t2.join();
return total.load();
}
};
template <int N>
struct Factorial {
static const int value = N * Factorial<N - 1>::value;
};
template <>
struct Factorial<0> {
static const int value = 1;
};
int main() {
std::cout << "Ready" << std::endl;
std::cout << Factorial<5>::value << std::endl;
std::cout << Engine::parallelSum();
return 0;
}Expected output
Ready 120 36
Questions and answers
Review the lesson questions together with their answers. To test yourself, solve the lesson interactively.
1 What does std::atomic provide when two threads update a shared counter?
std::atomic<int> total{0};- It prevents data races Correct answer
- It reduces memory usage
- It limits the number of threads
- It shortens compile time
Explanation: std::atomic makes every write atomic; no data race occurs.
2 Complete the atomic counter initial value
std::atomic<int> total{____};
std::atomic<int> total{0};
Explanation: The counter starts at zero and contributions are added.
3 Match each piece to its role
| Term | Meaning |
|---|---|
std::thread |
Launches a thread |
std::atomic<int> |
Shared safe counter |
t1.join() |
Waits for completion |
total += i |
Atomic addition |