Synchronization Primitives
Synchronization Primitives
A quick guide to mutexes, shared locks, semaphores, latches, barriers, and coordination patterns.
Synchronization Primitives
A quick guide to mutexes, shared locks, semaphores, latches, barriers, and coordination patterns.
std::mutex: exclusive lock.std::shared_mutex: many readers or one writer.std::condition_variable: wait for state changes.std::counting_semaphore: limit concurrent access to a pool.std::latch: one-shot rendezvous.std::barrier: repeated phase synchronization.std::mutex when one thread at a time may touch shared mutable state.std::shared_mutex when reads dominate and writes are relatively rare.std::condition_variable when threads should sleep until a state change happens.std::counting_semaphore when a fixed number of workers may enter a region concurrently.std::latch when multiple threads must finish setup before anyone continues.std::barrier when threads advance through multiple synchronized phases.std::shared_mutex mutex;
void read_value() {
std::shared_lock lock(mutex);
}
void write_value() {
std::unique_lock lock(mutex);
}
Use this only when you have measured read-heavy contention. A plain mutex is often simpler and fast enough.
std::counting_semaphore<4> slots(4);
Use semaphores when access should be capped instead of fully serialized.
slots.acquire();
use_connection();
slots.release();
This pattern fits connection pools, worker slots, or rate-limited resources.
latch: workers finish setup before the next step begins.barrier: multiple threads repeat phased work together.std::mutex guard;
std::condition_variable cv;
std::queue<int> work;
bool done = false;
void consumer() {
std::unique_lock lock(guard);
cv.wait(lock, [&] { return done || !work.empty(); });
}
Always wait with a predicate. That protects you from spurious wakeups and documents the actual condition being awaited.
latch is single-use: all participants arrive once, then continue.barrier is reusable: participants meet, continue to the next phase, and can meet again.std::latch ready(3);
ready.count_down();
ready.wait();
Atomics coordinate individual memory operations. Synchronization primitives coordinate larger invariants and ownership of critical sections. If your rule is about a whole data structure, a mutex is usually the right starting point.
#include <thread>
int main() {
std::jthread worker([] { /* coordinate work */ });
}
Add a stop condition or notification path. Concurrency examples become useful only once shutdown and coordination are explicit.
#include <stop_token>
#include <thread>
int main() {
std::jthread worker([](std::stop_token token) {
while (!token.stop_requested()) {
break;
}
});
}