CodeBlanks Logo Icon CodeBlanks

Concurrency

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

main.cpp
#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

main.cpp
#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?

main.cpp
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

Question
std::atomic<int> total{____};
Answer
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

All lessons in Expert