Synchronization Primitives

Synchronization Primitives

Coordinate threads with the right primitive instead of forcing every problem into a single mutex.

Synchronization Primitives

Start with the invariant

Before choosing a primitive, decide what you are protecting:

The primitive should match the coordination problem.

std::shared_mutex

Use this when reads are frequent and writes are rare.

std::shared_mutex cache_mutex;
std::unordered_map<std::string, int> cache;

int lookup(std::string_view key) {
    std::shared_lock lock(cache_mutex);
    return cache.at(std::string{key});
}

void update(std::string key, int value) {
    std::unique_lock lock(cache_mutex);
    cache[std::move(key)] = value;
}

Semaphores

Use a semaphore when several tasks may proceed concurrently, but only up to a fixed limit.

std::counting_semaphore<8> db_slots(8);

This is often a better fit than a mutex when you are modeling capacity instead of exclusive ownership.

Latches and barriers

Condition variables still matter

Condition variables remain the right tool when threads must sleep until a state predicate becomes true.

cv.wait(lock, [] { return ready; });

Exercises