Concurrency
- C++ course
- Advanced
- Lesson 81/83
- 6 min
Write parallel processing with std::thread and atomic.
Concurrency layer
In this lesson we add the parallel_sum method to Framework. This method starts a thread with std::thread and sums without data races using std::atomic.
Example: std::atomic<int> total{0}; std::thread t1([&]() { for (auto x : data) if (x > 0) total += x; }); t1.join();. std::atomic makes total += x atomic; this way multiple threads can safely write to the same variable. With join() we wait for the thread to finish. This brings parallel computation into our template. The previous lesson's process_data method stays in place.
Safe access with std::atomic
If multiple threads write to the same variable, a data race occurs and the behaviour is undefined. std::atomic solves this: each read/write becomes atomic.
Example: std::atomic<int> total{0};. total += x; is an atomic read-modify-write operation. When the thread is awaited with join(), the final value of total can be safely read. With return total; the atomic<int> is implicitly converted to int. This is the basis of lock-free parallel summation.
Practice Add the parallel_sum method
Add int parallel_sum(const std::vector<int>& data) to Framework. Body: std::atomic<int> total{0};, std::thread t1([&]() { for (auto x : data) if (x > 0) total += x; });, t1.join();, return total;. In run add std::cout << "parallel: " << parallel_sum(data); (also add << std::endl to the previous line).
Starter code
#include <iostream>
#include <string>
#include <vector>
#include <ranges>
#include <algorithm>
#include <concepts>
#include <thread>
#include <atomic>
class Framework {
public:
int run();
template<std::integral T>
int process_data(const std::vector<T>& data);
// Add the parallel_sum method (thread + atomic)
};
template<std::integral T>
int Framework::process_data(const std::vector<T>& data) {
int sum = 0;
for (auto x : data | std::views::filter([](T v) { return v > 0; })) {
sum += x;
}
return sum;
}
int Framework::run() {
std::cout << "Project ready" << std::endl;
std::vector<int> data{1, -2, 3};
std::cout << "sum: " << process_data(data) << std::endl;
// Call parallel_sum and print the result
return 0;
}
int main() {
Framework fw;
fw.run();
return 0;
}Sample solution
#include <iostream>
#include <string>
#include <vector>
#include <ranges>
#include <algorithm>
#include <concepts>
#include <thread>
#include <atomic>
class Framework {
public:
int run();
template<std::integral T>
int process_data(const std::vector<T>& data);
int parallel_sum(const std::vector<int>& data);
};
template<std::integral T>
int Framework::process_data(const std::vector<T>& data) {
int sum = 0;
for (auto x : data | std::views::filter([](T v) { return v > 0; })) {
sum += x;
}
return sum;
}
int Framework::parallel_sum(const std::vector<int>& data) {
std::atomic<int> total{0};
std::thread t1([&]() { for (auto x : data) if (x > 0) total += x; });
t1.join();
return total;
}
int Framework::run() {
std::cout << "Project ready" << std::endl;
std::vector<int> data{1, -2, 3};
std::cout << "sum: " << process_data(data) << std::endl;
std::cout << "parallel: " << parallel_sum(data);
return 0;
}
int main() {
Framework fw;
fw.run();
return 0;
}Expected output
Project ready sum: 4 parallel: 4
Questions and answers
Review the lesson questions together with their answers. To test yourself, solve the lesson interactively.
1 std::thread
std::____ t1([&]() { total += 1; });
std::thread t1([&]() { total += 1; });
Explanation: std::thread starts a thread.
2 What does std::atomic provide?
- Access to a shared variable without data races Correct answer
- Memory savings
- Less code
- Faster summation
Explanation: std::atomic provides access to a shared variable without data races.
3 Match each member to its role
| Term | Meaning |
|---|---|
std::thread |
Thread |
std::atomic |
Data race protection |
join() |
Waiting |
lambda |
Worker body |