C++ Concurrency Cheat Sheet
Quick reference for std::thread, mutex, atomic, condition_variable, and async in C++.
Thread lifecycle
cpp
#include <thread>
// Launch
std::thread t(func, arg1, arg2);
// Wait for completion
t.join();
// Detach (runs independently — beware dangling captures!)
t.detach();
// Check if joinable
if (t.joinable()) t.join();
// C++20: auto-joins on destruction
std::jthread jt(func); // no need to call join()Mutual exclusion
cpp
#include <mutex>
std::mutex mtx;
// RAII locks
std::lock_guard<std::mutex> lg(mtx); // lock on construction, unlock on destruction
std::unique_lock<std::mutex> ul(mtx); // same but can unlock() / relock()
std::scoped_lock sl(mtx1, mtx2); // C++17: lock multiple, no deadlock
// Manual (avoid if possible)
mtx.lock();
mtx.try_lock(); // returns false if already locked
mtx.unlock();
// Mutex types
std::mutex
std::recursive_mutex // same thread can re-lock
std::timed_mutex // try_lock_for, try_lock_until
std::shared_mutex // C++17: reader-writer
std::shared_lock<std::shared_mutex> sl(sm); // reader lockAtomics
cpp
#include <atomic>
std::atomic<int> n{0};
n.load(); // read
n.store(42); // write
n.exchange(10); // write, return old
n.fetch_add(1); ++n; // increment (returns old)
n.fetch_sub(1); --n; // decrement
n.fetch_and(mask); // bitwise AND
n.fetch_or(mask); // bitwise OR
// Compare-and-swap
int expected = 5;
n.compare_exchange_strong(expected, 6);
// true if swapped, false + expected=current if not
// Memory order (default: seq_cst)
n.store(1, std::memory_order_release);
n.load(std::memory_order_acquire);
n.fetch_add(1, std::memory_order_relaxed); // no sync
// Flag (lock-free guaranteed)
std::atomic_flag flag = ATOMIC_FLAG_INIT;
flag.test_and_set(); // set, return old value
flag.clear();Condition variable
cpp
#include <condition_variable>
std::mutex mtx;
std::condition_variable cv;
bool ready = false;
// Waiter
std::unique_lock<std::mutex> lock(mtx);
cv.wait(lock, []{ return ready; }); // always use predicate!
// Process...
// Notifier
{
std::lock_guard lock(mtx);
ready = true;
}
cv.notify_one(); // wake one waiter
cv.notify_all(); // wake all waitersFutures and async
cpp
#include <future>
// Launch async task
std::future<int> f = std::async(std::launch::async, []{ return 42; });
int result = f.get(); // blocks until ready
// Promise — set result from another thread
std::promise<int> p;
std::future<int> f = p.get_future();
std::thread([&p]{ p.set_value(100); }).detach();
f.get(); // 100
// Shared future — multiple readers
std::shared_future<int> sf = f.share();
// Packaged task
std::packaged_task<int(int)> task([](int n){ return n * 2; });
auto fut = task.get_future();
std::thread(std::move(task), 21).detach();
fut.get(); // 42Call once
cpp
std::once_flag flag;
std::call_once(flag, []{ /* runs exactly once */ });Thread-local storage
cpp
thread_local int per_thread_counter = 0;
// Each thread gets its own copyCommon patterns
cpp
// Producer-consumer queue
std::queue<Work> q;
std::mutex m;
std::condition_variable cv;
// Producer
{ std::lock_guard lock(m); q.push(item); }
cv.notify_one();
// Consumer
std::unique_lock lock(m);
cv.wait(lock, [&]{ return !q.empty(); });
auto item = q.front(); q.pop();
// Thread pool (simple)
std::vector<std::jthread> pool;
for (int i = 0; i < N; ++i)
pool.emplace_back([&queue, &cv, &m]{ /* worker loop */ });Memory order quick guide
| Order | Use case |
|---|---|
relaxed | Counters where ordering doesn't matter |
acquire | Load that starts a critical section |
release | Store that ends a critical section |
acq_rel | RMW (fetch_add) in the middle |
seq_cst | Default; full ordering (safest, slowest) |