Concurrency - Threads, Async, Atomics¶
C++ standard threading primitives: std::thread, std::async, std::mutex, std::atomic, std::condition_variable. Portable, zero-overhead abstractions over OS threading.
Key Facts¶
<thread>-std::thread,std::jthread(C++20, auto-joining)<mutex>-mutex,recursive_mutex,shared_mutex(C++17), lock guards<atomic>- lock-free atomic operations,std::atomic<T><future>-std::async,std::future,std::promise,std::packaged_task<condition_variable>- thread signaling, wait/notifystd::threadmust bejoin()ed ordetach()ed before destruction, elsestd::terminatestd::jthread(C++20) - auto-joins in destructor, supports cooperative cancellation viastop_tokenstd::async(std::launch::async, ...)guarantees new thread;std::launch::deferredis lazy- Data race = UB in C++. At least one write + concurrent access without synchronization = undefined
std::atomic<T>- lock-free for small types (int, ptr), provides happens-before orderingstd::shared_mutex(C++17) - multiple readers OR single writer
Patterns¶
Basic Threading¶
#include <thread>
void worker(int id, const std::string& msg) {
std::cout << "Thread " << id << ": " << msg << '\n';
}
// Launch threads
std::thread t1(worker, 1, "hello");
std::thread t2(worker, 2, "world");
t1.join(); // wait for completion
t2.join();
// C++20: jthread - auto-joining
std::jthread jt([](std::stop_token st) {
while (!st.stop_requested()) {
// do work
std::this_thread::sleep_for(std::chrono::milliseconds(100));
}
});
// jt automatically joins when destroyed
Mutex and Locks¶
#include <mutex>
std::mutex mtx;
int shared_counter = 0;
void increment() {
std::lock_guard lock(mtx); // CTAD, C++17
++shared_counter;
} // automatically unlocked
// Multiple mutexes - avoid deadlock
std::mutex m1, m2;
void safe_swap() {
std::scoped_lock lock(m1, m2); // C++17, deadlock-free
// swap protected data
}
// Reader-writer lock (C++17)
std::shared_mutex rw_mtx;
void reader() {
std::shared_lock lock(rw_mtx); // multiple readers OK
// read shared data
}
void writer() {
std::unique_lock lock(rw_mtx); // exclusive access
// modify shared data
}
async/future¶
#include <future>
// Launch async task
auto future = std::async(std::launch::async, [] {
std::this_thread::sleep_for(std::chrono::seconds(1));
return 42;
});
// Do other work...
int result = future.get(); // blocks until ready
// Promise/future pair for manual control
std::promise<int> prom;
std::future<int> fut = prom.get_future();
std::thread t([&prom] {
prom.set_value(compute_result());
});
int val = fut.get();
t.join();
Atomic Operations¶
#include <atomic>
std::atomic<int> counter{0};
std::atomic<bool> ready{false};
void producer() {
// prepare data...
ready.store(true, std::memory_order_release);
}
void consumer() {
while (!ready.load(std::memory_order_acquire)) {
std::this_thread::yield();
}
// data is ready
}
// Atomic increment - thread-safe without mutex
void count() {
counter.fetch_add(1, std::memory_order_relaxed);
// or simply:
++counter; // uses sequentially consistent ordering
}
Condition Variable¶
std::mutex mtx;
std::condition_variable cv;
std::queue<int> work_queue;
bool done = false;
void producer() {
for (int i = 0; i < 10; ++i) {
{
std::lock_guard lock(mtx);
work_queue.push(i);
}
cv.notify_one();
}
{
std::lock_guard lock(mtx);
done = true;
}
cv.notify_all();
}
void consumer() {
while (true) {
std::unique_lock lock(mtx);
cv.wait(lock, [] { return !work_queue.empty() || done; });
if (work_queue.empty() && done) break;
int item = work_queue.front();
work_queue.pop();
lock.unlock();
process(item);
}
}
Gotchas¶
- Issue: Forgetting to
join()ordetach()std::thread->std::terminatecalled -> Fix: Usestd::jthread(C++20) or ensure join/detach in all code paths including exceptions - Issue: Locking multiple mutexes in different order -> deadlock -> Fix: Use
std::scoped_lock(m1, m2)(C++17) which uses deadlock-avoidance algorithm - Issue:
std::asyncwith default policy may run deferred (same thread) -> Fix: Explicitly usestd::launch::asyncif you need a new thread - Issue: Condition variable spurious wakeups -> Fix: Always use predicate form:
cv.wait(lock, predicate) - Issue:
std::coutinterleaving from multiple threads -> garbled output -> Fix: Usestd::osyncstream(C++20) or protect with mutex