Concurrency in C++

innocentzero

2026-03-14

Concurrency in C++

concurrency

C++ 11: Threads

Memory model

User threads: tasks executed by pools of kernel threads

std::thread is a user thread.

Problems with threads

Thread overhead - if multiple active kernel threads are computing for much fewer processes.

Since std::thread is user thread, there shouldn't be a problem but since most OS's offer 1:1 thread models, there is. At best, we end up with a badly broken N:M model. A new thread for every computation makes the program slower.

Threads are expensive to start and join. (in 100s of microseconds per thread)

How to improve perf

Manage threads yourself. Keep them alive for a long time. One thread per core. Give it work once it is done with existing work. Else idle.

Futures and promises

Locks

std::mutex

Very awful example of a mutex - can lead to a deadlock. Nobody outside of the thread function knows that there is a lock and that has to be freed.

int sum = 0;
std::mutex sum_mutex;
void thread_worker(int i) {
sum_mutex.lock();
sum += i;
sum_mutex.unlock();
}

std::lock_guard: RAII for mutexes

Constructor locks, destructor unlocks

void thread_worker(int i) {
std::lock_guard(sum_mutex);
sum += i;
}

std::unique_lock: moving ownership of the mutex, kinda like unique_ptr

std::mutex m;
m.lock(); //thread 1
m.lock(); //thread 2

Only thread 1 can unlock the mutex. Thread 2 locking it causes a deadlock since instructions can't move forward.

If for some reason you have to do that, std::recursive_mutex (keeps a reference count).

Interesting scenario of a deadlock.

std::mutex m1, m2;
//thread 1
m1.lock();
m2.lock();

//thread 2
m2.lock();
m1.lock();

Both are waiting for the other thread to release the lock first. Not happening.

Solution - std::lock(m1, m2, m3, ...) - guarantees no deadlock. - used with unique_lock to unlock automatically. - Alternatively use std::scoped_lock - std::lock is a function, so you still need to manually release them somewhere. std::scoped lock wraps the unlock in its destructor. cpp std::scoped_lock l(m1, m2, m3, ...);

shared_mutex - read write lock

Unlike lock(), lock_shared() only gives you read access. Good in theory, performance not so good in practice. RAII wrapper is std::shared_lock for the same.

Condition Variables

Two threads - producer and consumer

std::condition_variable c;
std::mutex m;

// producer
Data *data = nullptr;
{std::locked_guard l(m); data = new Data;} c.notify_all();

//consumer

std::unique_lock l(m);
c.wait(l, [&]{return bool(data);});
do_work(data);

Function calls in concurrency

std::call_once() & std::once_flag to be used in conjunction.

std::call_once(/* std::once_flag */ done, []{cout << "running" << endl;});

thread_local like static but one copy per thread. So the same variable will have multiple addresses.

std::latch - synchronization barrier without explicitly joining the thread back to main function. Latches cannot be used multiple times.

Syntax : std::counting_semaphore<max_num>::release() for releasing the locks and std::counting_semaphore<max_num>::acquire() to acquire a resource.