C++ Multithreading

C++ multithreading lets a program run more than one flow of work at the same time. A thread is a separate path of execution inside the same program.

Threads are useful for work that can happen independently, such as background calculations, file processing, server requests, or keeping an interface responsive. Because threads share the same program memory, you must also protect shared data from unsafe access.

Creating A Thread

The standard library provides std::thread in the <thread> header. You create a thread by passing it a function and the arguments for that function.

After starting a thread, call join() before the std::thread object is destroyed. Joining means the current thread waits until that worker thread finishes. If a joinable thread object is destroyed without being joined or detached, the program terminates.

Shared Data And Data Races

Threads can access the same variables, but that is dangerous when at least one thread writes to the data. If two threads read and write the same object at the same time without synchronization, the program has a data race. A data race makes the program’s behavior undefined.

A std::mutex protects a critical section: the part of the code that touches shared data. Only one thread can hold the mutex at a time. The RAII helper std::lock_guard locks the mutex when it is created and unlocks it automatically when it goes out of scope.

Example: Adding Work From Two Threads

This program starts two threads. Each thread calculates a local sum first, then safely adds that local result to the shared total.

#include <functional>
#include <iostream>
#include <mutex>
#include <thread>

void addRange(int start, int end, int& total, std::mutex& totalMutex) {
    int localSum = 0;

    for (int number = start; number <= end; number++) {
        localSum += number;
    }

    std::lock_guard<std::mutex> guard(totalMutex);
    total += localSum;
}

int main(void) {
    int total = 0;
    std::mutex totalMutex;

    std::thread first(addRange, 1, 5, std::ref(total), std::ref(totalMutex));
    std::thread second(addRange, 6, 10, std::ref(total), std::ref(totalMutex));

    first.join();
    second.join();

    std::cout << "Total: " << total << std::endl;

    return 0;
}

Output:

Total: 55

How The Example Works

addRange() receives a start value, an end value, a reference to the shared total, and a reference to the mutex that protects it. The references are passed to each thread with std::ref(), because thread arguments are copied by default.

Each thread computes localSum without touching shared data. That keeps the locked section small. When the thread is ready to update total, it creates a std::lock_guard<std::mutex>. The lock guard locks totalMutex, performs the update, then unlocks the mutex automatically at the end of the function.

main() calls join() on both threads before printing the result. This guarantees both threads have finished adding their ranges before total is displayed.

Common Threading Rules

  • Always join() or detach() a std::thread before its object is destroyed.
  • Prefer join() when the main program needs the worker’s result.
  • Protect shared writable data with synchronization such as std::mutex.
  • Use RAII locking helpers such as std::lock_guard instead of manually calling lock() and unlock().
  • Keep locked sections short so other threads do not wait longer than necessary.

When To Be Careful

More threads do not automatically make a program faster. Thread creation has overhead, and incorrect synchronization can create bugs that are hard to reproduce. Problems such as data races, deadlocks, and output appearing in different orders are common beginner mistakes.

Start with clear ownership of data. Let each thread work on local data when possible, and synchronize only the small step where shared state must be updated.

Takeaway: C++ threads let work run concurrently, but shared data must be synchronized carefully with tools such as mutexes and lock guards.