Concurrency

Multithreading and synchronization in modern C++

Thread Basics

#include <thread>
#include <future>

// Creating threads
std::thread t1([]() {
    std::cout << "Hello from thread" << std::endl;
});

t1.join();  // Wait for completion

// Thread with arguments
void worker(int id, const std::string& name);
std::thread t2(worker, 42, "Worker");
t2.join();

// Async - simpler thread management
auto future = std::async(std::launch::async, []() {
    return compute_something();
});
int result = future.get();  // Blocks until ready

// Thread pool with jthread (C++20)
std::vector<std::jthread> workers;
for (int i = 0; i < 4; ++i) {
    workers.emplace_back([i]() {
        // Work on task i
    });
}
// jthread automatically joins on destruction

Synchronization

#include <mutex>
#include <shared_mutex>

std::mutex mtx;
int shared_data = 0;

// Basic locking
{
    std::lock_guard<std::mutex> lock(mtx);
    ++shared_data;
}  // Automatically unlocks

// Unique lock - more flexible
{
    std::unique_lock<std::mutex> lock(mtx);
    // Can unlock early
    lock.unlock();
    // Do something without lock...
    lock.lock();
}

// Shared mutex - multiple readers, one writer
std::shared_mutex rw_mtx;

// Readers
std::shared_lock<std::shared_mutex> read_lock(rw_mtx);
// Multiple threads can hold shared_lock simultaneously

// Writer
std::unique_lock<std::shared_mutex> write_lock(rw_mtx);
// Exclusive access

Condition Variables

#include <condition_variable>

std::mutex mtx;
std::condition_variable cv;
std::queue<int> queue;
bool done = false;

// Producer
void producer() {
    for (int i = 0; i < 10; ++i) {
        {
            std::lock_guard<std::mutex> lock(mtx);
            queue.push(i);
        }
        cv.notify_one();  // Notify consumer
    }
    
    {
        std::lock_guard<std::mutex> lock(mtx);
        done = true;
    }
    cv.notify_all();
}

// Consumer
void consumer() {
    while (true) {
        std::unique_lock<std::mutex> lock(mtx);
        cv.wait(lock, []() { 
            return !queue.empty() || done; 
        });
        
        while (!queue.empty()) {
            int value = queue.front();
            queue.pop();
            lock.unlock();
            process(value);
            lock.lock();
        }
        
        if (done && queue.empty()) break;
    }
}

Atomic Operations

#include <atomic>

std::atomic<int> counter{0};

// Lock-free increment
counter.fetch_add(1);  // ++counter
counter.fetch_sub(1);  // --counter

// Compare and swap (CAS)
int expected = 0;
bool success = counter.compare_exchange_strong(
    expected, 1
);
// If counter == expected, sets counter to 1 and returns true
// Otherwise, sets expected to counter value and returns false

// Memory ordering
std::atomic<int> flag{0};
int data = 0;

// Thread 1
data = 42;
flag.store(1, std::memory_order_release);

// Thread 2
if (flag.load(std::memory_order_acquire) == 1) {
    // Guaranteed to see data == 42
}