Search Knowledge

© 2026 LIBREUNI PROJECT

Modern C++ Programming / Concurrency

Multithreading Basics

Multithreading: Concurrent Execution

C++11 introduced a standard threading library (<thread>), enabling portable concurrent programming without platform-specific APIs.

Creating Threads

#include <thread>
#include <iostream>

void task(int id) {
    std::cout << "Thread " << id << " executing\\n";
}

int main() {
    std::thread t1(task, 1);
    std::thread t2(task, 2);
    
    t1.join();  // Wait for t1 to finish
    t2.join();  // Wait for t2 to finish
    
    return 0;
}

Thread Operations

Join vs Detach

std::thread t(task);

// Option 1: Join (wait for completion)
t.join();  // Blocks until thread completes

// Option 2: Detach (independent execution)
t.detach();  // Thread runs independently
// Cannot join() after detach()

Critical: Every thread must be either joined or detached before destruction, otherwise std::terminate is called.

{
    std::thread t(task);
}  // CRASH: thread destructor called without join/detach

Thread Guard RAII

class thread_guard {
    std::thread& t_;
public:
    explicit thread_guard(std::thread& t) : t_(t) {}
    ~thread_guard() {
        if (t_.joinable()) t_.join();
    }
    thread_guard(const thread_guard&) = delete;
    thread_guard& operator=(const thread_guard&) = delete;
};

void func() {
    std::thread t(task);
    thread_guard g(t);
    // Even if exception thrown, thread is joined
}

Passing Arguments

void task(int x, const std::string& s) {
    std::cout << x << ": " << s << '\\n';
}

std::thread t(task, 42, "hello");  // Args passed by value

Reference Arguments

void modify(int& x) {
    x = 100;
}

int value = 0;
std::thread t(modify, std::ref(value));  // Must use std::ref
t.join();
std::cout << value;  // 100

Data Races and Undefined Behavior

Concurrent modification of shared data without synchronization causes data races (undefined behavior):

int counter = 0;

void increment() {
    for (int i = 0; i < 100000; ++i) {
        ++counter;  // DATA RACE!
    }
}

std::thread t1(increment);
std::thread t2(increment);
t1.join();
t2.join();

std::cout << counter;  // Unpredictable result (< 200000)

Mutexes: Mutual Exclusion

std::mutex provides mutual exclusion:

#include <mutex>

std::mutex mtx;
int counter = 0;

void increment() {
    for (int i = 0; i < 100000; ++i) {
        mtx.lock();
        ++counter;
        mtx.unlock();
    }
}

Problem: If exception thrown between lock/unlock, mutex stays locked (deadlock).

Lock Guards: RAII for Mutexes

std::mutex mtx;
int counter = 0;

void increment() {
    for (int i = 0; i < 100000; ++i) {
        std::lock_guard<std::mutex> lock(mtx);  // Locks mutex
        ++counter;
    }  // Automatically unlocks when lock goes out of scope
}

unique_lock: Flexible Locking

More flexible than lock_guard:

std::mutex mtx;

void task() {
    std::unique_lock<std::mutex> lock(mtx);
    
    // Do work...
    
    lock.unlock();  // Manually unlock
    
    // Do work without lock...
    
    lock.lock();  // Relock
    
    // More work...
}  // Automatically unlocks if locked

Deferred Locking

std::unique_lock<std::mutex> lock(mtx, std::defer_lock);  // Don't lock yet
// ...
lock.lock();  // Lock when ready

Avoiding Deadlocks

Deadlock Example

std::mutex m1, m2;

void thread1() {
    std::lock_guard<std::mutex> lock1(m1);
    std::lock_guard<std::mutex> lock2(m2);  // Potential deadlock
}

void thread2() {
    std::lock_guard<std::mutex> lock2(m2);
    std::lock_guard<std::mutex> lock1(m1);  // Opposite order
}

Solution: std::lock

void thread1() {
    std::unique_lock<std::mutex> lock1(m1, std::defer_lock);
    std::unique_lock<std::mutex> lock2(m2, std::defer_lock);
    std::lock(lock1, lock2);  // Atomically locks both, deadlock-free
}

C++17 std::scoped_lock simplifies this:

void thread1() {
    std::scoped_lock lock(m1, m2);  // Locks both, unlocks in destructor
}

Thread Identification

std::thread::id main_thread_id = std::this_thread::get_id();

void task() {
    auto id = std::this_thread::get_id();
    std::cout << "Thread ID: " << id << '\\n';
}

Hardware Concurrency

unsigned int n = std::thread::hardware_concurrency();
std::cout << "Hardware supports " << n << " concurrent threads\\n";
Conceptual Check

What happens if a thread object is destroyed without calling join() or detach()?

Runtime Environment

Interactive Lab

1#include <iostream>
2#include <thread>
3#include <mutex>
4#include <vector>
5 
6std::mutex mtx;
7int counter = 0;
8 
9void increment(int count) {
10 for (int i = 0; i < count; ++i) {
11 std::lock_guard<std::mutex> lock(mtx);
12 ++counter;
13 }
14}
15 
16int main() {
17 const int iterations = 10000;
18 std::vector<std::thread> threads;
19
20 for (int i = 0; i < 4; ++i) {
21 threads.emplace_back(increment, iterations);
22 }
23
24 for (auto& t : threads) {
25 t.join();
26 }
27
28 std::cout << "Counter: " << counter << '\n';
29 std::cout << "Expected: " << (4 * iterations) << '\n';
30
31 return 0;
32}
System Console

Waiting for signal...