Which guarantee does this primitive give, and which does it not?

Synchronization Primitives

Mutexes and the scope you put them around, read/write locks, semaphores as permit counters, condition variables and the predicate loop — with lost and spurious wakeups as the failures that follow from getting the loop wrong.

Mutexes: What They Protect and What They Do Not▶ lab

A mutex gives you one thing: at most one task inside the region at a time. Operating Systems covers how that is implemented. What matters here is which invariant a given mutex is protecting, what it costs the tasks that wait, and the long list of things engineers expect from it that it does not provide.

Q · Which invariant does this particular mutex protect, and which of the guarantees I am assuming does it actually give me?

Lock Scope: What You Hold It Across▶ lab

The single highest-yield concurrency review question: what is inside this lock that does not need to be? A lock held across a network call has a hold time set by someone else's p99, which makes their outage your outage and their latency your throughput ceiling.

Q · What is inside this critical section that does not belong there, and what does keeping it there cost?

Read/Write Locks, Honestly▶ lab

Many readers or one writer. The idea is obviously good and the practice frequently is not: a read/write lock has a more expensive uncontended path than a mutex, it can starve writers, and it only pays when reads dominate *and* the region is long enough for the concurrency to matter. Most of the time a plain mutex or an immutable snapshot wins.

Q · Does allowing readers to proceed in parallel actually pay here, or am I adding overhead and a starvation risk for nothing?

Semaphores: Counting Permits as a Resource Limit▶ lab

A semaphore is a counter with a waiting room. Its natural use is not mutual exclusion but *resource limiting*: at most N tasks may be doing this at once, where N is the size of something real — a connection pool, an upload buffer, a downstream partner's rate limit. The failure that matters is the leaked permit.

Q · How do I express "at most N of these at a time", and what happens to the permit when the task in the middle throws?

Semaphore versus Mutex: Not the Same Primitive▶ lab

A binary semaphore and a mutex both admit one task at a time, which is where the similarity ends. A mutex has an owner; a semaphore has a count. That single difference decides reentrancy, priority inheritance, who is allowed to release, whether the primitive can signal across tasks, and which failures are possible at all.

Q · When the two primitives look identical at N = 1, what actually differs, and which failures does each one make possible?

Condition Variables: Waiting Until a Predicate Is True▶ lab

A mutex answers "may I touch this?". A condition variable answers "is it worth touching yet?". It lets a task release the lock and sleep until another task says the state changed — and every correct use of one is built around a predicate checked in a loop, under the lock, both before waiting and after waking.

Q · How does a task wait for a *condition* on shared state without holding the lock and without spinning?

Lost Wakeups: The Notify That Arrived Before the Wait▶ lab

A condition variable stores nothing. If the state changes and the notification is sent at a moment when nobody is waiting, that notification is discarded — and the task that arrives a microsecond later waits forever for an event that already happened. The fix is not a bigger buffer or a retry; it is checking the predicate under the same lock that guards the state change.

Q · Why does a task wait forever for a condition that is already true, and what exactly must be under the lock to prevent it?

Spurious Wakeups: Why It Is `while`, Not `if`▶ lab

A condition variable may return from wait without any notification having been sent. That is permitted behaviour in POSIX, in C++, in Java and in Python — and even where it is not, another task can consume the state between the notify and your wake. Both reasons lead to the same rule: re-check the predicate in a loop, always.

Q · Why must the predicate be re-checked after `wait` returns, and which languages and APIs does that apply to?