Semaphore vs mutex comes down to ownership. A mutex is a lock: one thread locks it, and only that same thread may unlock it. A semaphore works differently. It is an integer counter with two atomic operations, wait and signal. Any thread may call signal, even one that never called wait. So a mutex protects a critical section through ownership. A semaphore instead controls a pool of resources, or signals between threads, with no owner at all. A binary semaphore looks like a mutex, since its value sits at 0 or 1. Still, it has no owner, so it cannot substitute for one. The classic bounded-buffer solution needs both together: two counting semaphores for the slots, plus a mutex for the buffer itself.
Two threads write to one shared variable, and the result depends on timing. That is a race condition, and every operating system needs a fix for it. Semaphore vs mutex names the two tools taught in almost every OS course to solve it.
GATE and university papers test both objects directly. A question might ask why signal has no ownership check. Another might ask you to trace a bounded-buffer solution step by step. Interviewers ask something similar, just phrased less formally: what actually stops two threads from colliding.
This guide builds both concepts from the ground up, then works through the classic producer-consumer problem together. It also shows the exact swapped line that turns a working solution into a deadlock. The threads competing for these locks are covered in more depth in process vs thread. That is worth a look if the groundwork feels shaky.

The Problem Both Solve
A critical section is the part of a program that touches shared data. When two threads enter it at once, updates can overlap and vanish. That outcome is called a race condition, and it depends entirely on timing.
Timing goes wrong because a thread can be interrupted mid-section, not just at safe boundaries. A scheduler can preempt it after it reads a value but before it writes the updated one back. Preemption is covered fully in preemptive vs non-preemptive scheduling. It is exactly why a critical section needs protection at all.
So an operating system needs a way to let only one thread inside a critical section at a time. Mutual exclusion is the general name for that guarantee. Mutexes and semaphores are the two classic tools built to enforce it.
Both tools block a thread that cannot proceed yet, instead of letting it barge in. Both wake a blocked thread once the section is free again. Where they differ is ownership, and that single difference shapes almost everything else about them.
What a Mutex Is
A mutex, short for mutual exclusion, is a locking mechanism. It exists to guard exactly one critical section, so only one thread sits inside it at any moment.
A thread calls lock before entering the section, and calls unlock on the way out. If the mutex is already locked, the calling thread blocks until it becomes free. Once unlocked, one waiting thread gets to proceed.
Ownership is the defining rule. Whichever thread locks a mutex is the only thread allowed to unlock it. Another thread cannot release a lock it never took, and a correct implementation enforces that directly.
A mutex holds only two states, locked or unlocked. There is no counter behind it, and it cannot be initialised to a value above one. Every lock call either succeeds outright or blocks; nothing in between exists.
Because ownership is tracked, a mutex can support priority inheritance. A low-priority thread holding the lock can be boosted temporarily. So a higher-priority thread waiting on it is not stuck behind unrelated work. That mechanism limits priority inversion, a scheduling hazard on its own.
Advantages of a mutex.
- Ownership makes misuse easier to catch: only the locking thread may unlock it.
- Supports priority inheritance, which helps bound priority inversion.
- Simple binary state, so its behaviour is easy to reason about.
Disadvantages of a mutex.
- Protects only one critical section; it was never meant to count resources.
- If the owning thread exits without unlocking, other threads can block forever.
- Cannot signal an event to a thread that never locked it, since ownership rules that out.
What a Semaphore Is
A semaphore is a signalling mechanism, introduced by Edsger Dijkstra. It is an integer variable, accessed only through two atomic operations, so no thread can read or change it halfway.
The first operation, wait, historically called P, from the Dutch proberen, decrements the counter. If the result would fall below zero, the calling thread blocks until another thread signals.
The second operation, signal, historically called V, from verhogen, increments the counter. It can wake a thread that is currently blocked in wait, if one is waiting.
A counting semaphore allows the value to rise above one. So it can guard a pool of N identical resources at once. A binary semaphore restricts the value to 0 or 1, closer in shape to a mutex, though not in behaviour.
No ownership rule applies here. Any thread may call signal, including a thread that never called wait on that semaphore at all. That property is exactly what separates a semaphore from a mutex. It is worth remembering for exam questions phrased either way.
Advantages of a semaphore.
- Counts a pool of resources directly, through one integer value.
- Signals between threads cleanly, since no ownership check gets in the way.
- Works well alongside a mutex, as the bounded-buffer example below shows.
Disadvantages of a semaphore.
- No ownership means a bug can call signal without a matching wait, corrupting the count.
- Debugging is harder, since any thread can touch the value.
- A binary semaphore is easy to mistake for a mutex, though it lacks that protection.
Semaphore vs Mutex: Comparison Table

| Aspect | Semaphore | Mutex |
|---|---|---|
| Mechanism type | Signalling mechanism | Locking mechanism |
| Introduced by | Edsger Dijkstra | No single named inventor |
| Ownership | None; any thread may act on it | Owned by the thread that locked it |
| Underlying value | Integer counter | Two states only, locked or unlocked |
| Possible values | 0 upward (counting), or 0 and 1 (binary) | Locked or unlocked, nothing else |
| Core operations | wait() and signal(), historically P and V | lock() and unlock() |
| Who may release it | Any thread, including one that never waited | Only the thread that locked it |
| Counting variant | Yes, counts N identical resources | No, always binary in effect |
| Signalling between threads | Its core purpose | Not designed for this |
| Protecting a critical section | Possible in binary form | Purpose-built for this |
| Priority inheritance support | Not supported | Supported, because of ownership |
| Typical failure mode | A missing or extra signal corrupts the count | Deadlock from lock order, or a forgotten unlock |
| What it is for, in one phrase | Counting resources and signalling | Exclusive access to one section |
| Role in the bounded-buffer solution | Counts empty and full slots | Protects the buffer itself |
Worked Example: The Bounded Buffer
A producer and a consumer share a buffer of N slots. On their own, neither a mutex nor a semaphore solves this problem fully. So the classic solution uses three synchronisation objects together.
The counting semaphore empty starts at N and tracks free slots. The counting semaphore full starts at 0 and tracks filled slots. A mutex, initialised to 1, protects the buffer itself.

Here is the producer, in pseudocode. It is not a runnable program, just the sequence of operations that matters for the trace.
wait(empty)
wait(mutex)
add item to buffer
signal(mutex)
signal(full)The producer first waits on empty, claiming one free slot before doing anything else. Then it waits on mutex, so it holds exclusive access while it adds the item. It signals mutex right after, releasing that access, then signals full, announcing one more filled slot.
The consumer mirrors this exactly, with full and empty swapped.
wait(full)
wait(mutex)
remove item from buffer
signal(mutex)
signal(empty)The consumer waits on full first, claiming one filled slot to remove. It then waits on mutex, removes the item, and signals mutex. Finally it signals empty, returning one free slot to the pool.
Notice what each object is doing here. The two semaphores count resources, empty slots and full slots, and let a thread block until one is available. The mutex, meanwhile, protects the buffer data structure itself, so only one thread edits it at a time.
Most articles frame semaphore vs mutex as an either-or choice. That framing is wrong. The bounded-buffer solution needs both, working together, each covering a job the other cannot do alone.
One Swapped Line Causes Deadlock
Order matters in this solution, and a small change proves why. Suppose the producer swaps its first two lines instead.
wait(mutex)
wait(empty)Now trace what happens when the buffer is already full. The producer locks mutex first, then calls wait on empty. Since empty is 0, it blocks, but it still holds the mutex while blocked.
The consumer needs that same mutex before it can remove anything and free a slot. Since the producer is holding it, the consumer blocks too. Neither thread can proceed from here, and the program stalls permanently.
The rule to remember: always acquire the counting semaphore before the mutex, never after. That order keeps a blocked wait from ever happening while a lock is still held.
Semaphores are not the only source of this trap, either. Two threads locking two mutexes in opposite orders can deadlock the same way, with no semaphore involved at all. Ordering discipline matters for any lock, not just this one.
Binary Semaphore Is Not a Mutex
A binary semaphore looks tempting as a stand-in for a mutex. Its value sits at 0 or 1, the same two states a mutex holds. So the resemblance seems close on the surface.
Ownership is where the resemblance ends. A mutex tracks which thread locked it, and only that thread may unlock it. A binary semaphore tracks no such thing. So any thread may call signal on it, whether or not that thread ever called wait.
That gap opens real bugs. A thread could accidentally signal a binary semaphore twice, letting two threads into a section meant for one. A mutex implementation can catch a mismatched unlock; a binary semaphore generally cannot, since it never recorded who took it.
Priority inheritance depends on the same ownership record. Since a binary semaphore keeps none, it cannot support that mechanism, while a correctly built mutex can.
So treat a binary semaphore as what it is: a counter capped at one, useful for signalling. It is not a substitute for a mutex, whatever the value range might suggest.
When to Use Which
Reach for a mutex when one specific critical section needs protecting. The same thread will always do the locking and unlocking, and that ownership guarantee is the whole reason to pick it.
Reach for a semaphore when counting matters, such as limiting access to a pool of N identical resources. Also reach for one when a thread must signal another that never waited.
Scheduling context matters too. A thread synchronising through either tool can still be preempted mid-run by the CPU scheduler. So correct locking has to hold up under interruption, not just under ideal timing.
Systems running many processes at once, the kind compared in multiprogramming vs multiprocessing, lean on both tools constantly. A single system rarely picks one and avoids the other. Most real code uses each where it fits, exactly as the bounded-buffer example does.
Interview Questions
Frequently Asked Questions
Wrapping Up
Semaphore vs mutex comes down to one idea worth repeating. A mutex is a lock with an owner. A semaphore is a counter and a signal with no owner at all.
Keep the bounded-buffer example close for your exam. It is not a choice between the two tools. The classic solution needs a mutex and two counting semaphores working together, each covering a job the other cannot.
Finally, remember the ordering trap. Acquire the counting semaphore before the mutex, never after. Otherwise a producer can hold a lock the consumer needs while it waits for a slot that never comes.
Related reading on DiffStudy:
- Process vs Thread
- Preemptive vs Non-Preemptive Scheduling
- Job Scheduler vs CPU Scheduler
- Multiprogramming vs Multiprocessing
- CS Fundamentals hub