Articles

Mutex vs Semaphore: What's the Actual Difference

A mutex lets exactly one thread hold a lock at a time; a semaphore allows a fixed number of concurrent holders. Here's what that difference means in practice.

The Lycoris Team The Lycoris Team · · 4 min read
A chalkboard covered in diagrams and equations

A mutex allows exactly one thread to hold a lock at a time; a semaphore allows up to a fixed number of threads to hold access simultaneously, tracked with a counter. They’re both synchronization primitives used to control access to a shared resource, and they’re often confused because a semaphore with a count of one looks a lot like a mutex from the outside. The difference that actually matters is ownership: a mutex is owned by whichever thread locked it, and only that thread can unlock it, while a semaphore has no concept of ownership at all.

What problem they both solve

When multiple threads access the same resource — a shared data structure, a file handle, a connection pool — without coordination, you get a race condition: the outcome depends on unpredictable timing, and bugs show up intermittently, often only under load, which makes them notoriously hard to reproduce and debug. Synchronization primitives exist to make sections of code that touch shared state execute one at a time, or in some controlled, bounded way, rather than however the scheduler happens to interleave threads.

Mutex: mutual exclusion, one at a time

A mutex (“mutual exclusion”) is the simpler of the two. It has two states — locked and unlocked — and enforces a strict rule: only the thread that locked it can unlock it. If another thread tries to lock an already-locked mutex, it blocks until the owner releases it.

That ownership rule is the whole point. It lets the runtime or the operating system catch misuse — a thread trying to unlock a mutex it never locked is a bug the system can often detect and reject. It also enables priority inheritance schemes, where a low-priority thread holding a lock a high-priority thread is waiting on gets temporarily boosted, preventing the high-priority thread from being stuck behind unrelated lower-priority work.

Mutexes protect critical sections — the smallest possible block of code that touches shared state. The pattern is almost always: lock, do the minimal necessary work, unlock. Holding a mutex longer than necessary, or worse, while making a blocking call like a network request, is one of the most common sources of poor concurrent performance.

Semaphore: counting access, no ownership

A semaphore is built around an integer counter and two operations, traditionally called wait (or P) and signal (or V). wait decrements the counter and blocks if the result would go negative; signal increments it. A semaphore initialized to N allows up to N threads to hold it concurrently — the (N+1)th thread blocks until one of the current holders signals.

Crucially, any thread can call signal, not just the one that called wait. There’s no ownership relationship between a semaphore and the thread using it — which makes semaphores useful for a wider range of patterns than a mutex, but also removes a whole category of safety checks a mutex gives you for free.

A binary semaphore — one initialized to 1 — behaves similarly to a mutex in that only one holder is allowed at a time, but it still lacks ownership, which is why it’s a poor substitute for a mutex in most codebases: nothing stops a different thread from releasing it, which can mask bugs rather than surface them.

MutexSemaphore
Concurrent holdersExactly 1Up to N (set at creation)
OwnershipOnly the locking thread can unlockAny thread can signal
Typical useProtecting a single critical sectionLimiting concurrent access to a pool of N resources
Misuse detectionOften caught (wrong thread unlocking)Not enforced — a logic error, not a runtime error
Priority inheritanceCommonly supportedNot applicable — no owner to boost

When to actually reach for each

Use a mutex when you’re protecting a single shared piece of state — a cache, a counter, a data structure — and you want the simplest, safest primitive with the fewest ways to misuse it. This covers the overwhelming majority of concurrency problems in application code.

Use a semaphore when you’re limiting concurrent access to a pool of interchangeable resources rather than protecting a single piece of state — capping the number of simultaneous connections to a database, limiting how many worker threads can run a particular expensive operation at once, or throttling concurrent requests to a rate-limited external API. The counter maps naturally onto “how many of this resource exist,” in a way a mutex’s binary lock doesn’t.

Where deadlocks still lurk

Neither primitive protects you from deadlock on its own. The classic setup is two threads each holding one lock and waiting on the other — the dining philosophers problem is the canonical illustration of exactly this failure mode with more than two participants. The usual mitigations apply to both mutexes and semaphores: always acquire locks in a consistent global order across every code path, keep critical sections short, and avoid making a blocking call while holding a lock whenever possible.

At larger scale, when the “shared resource” isn’t in-process memory but something contended across machines — a distributed cache entry, a job that must run on exactly one worker — a plain mutex or semaphore doesn’t apply, and you need a distributed lock built on a consensus mechanism instead.

The takeaway

A mutex enforces exclusive, single-owner access to one critical section; a semaphore uses a counter to allow a bounded number of concurrent holders, with no ownership enforcement at all. Reach for a mutex by default when protecting shared state, and reach for a semaphore specifically when you’re limiting concurrency against a pool of N interchangeable resources — and in either case, keep critical sections short and lock acquisition order consistent, since that discipline, not the choice of primitive, is what actually prevents deadlocks.

The Lycoris Team The Lycoris Team · · 4 min read

What Is a Semaphore? Concurrency Control Explained

A semaphore is a counter that limits how many threads can access a resource at once. How they work, semaphores vs mutexes, and common pitfalls.

#Computer Science #Concurrency #Programming
The Lycoris Team The Lycoris Team · · 5 min read

The Dining Philosophers Problem, Explained

The dining philosophers problem is a classic model of deadlock and starvation: five philosophers, five forks, and a resource-sharing rule that can lock up.

#Computer Science #Concurrency #Programming
The Lycoris Team The Lycoris Team · · 4 min read

The Producer-Consumer Problem, Explained

The producer-consumer problem is a classic concurrency pattern: coordinating producers and consumers around a shared, bounded buffer safely.

#Computer Science #Concurrency #Programming