The short answer

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.

Two panels comparing a mutex, where one thread has passed a barrier and two others wait behind it labelled one at a time, against a semaphore with a counter reading 3 and three threads past the barrier labelled up to N
A mutex lets one thread through at a time; a semaphore lets up to N.

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

Infographic comparing mutex and semaphore across mechanism type, ownership, underlying value, and what each is used for
Mutex vs semaphore at a glance: type, ownership, value, and typical use.
AspectSemaphoreMutex
Mechanism typeSignalling mechanismLocking mechanism
Introduced byEdsger DijkstraNo single named inventor
OwnershipNone; any thread may act on itOwned by the thread that locked it
Underlying valueInteger counterTwo states only, locked or unlocked
Possible values0 upward (counting), or 0 and 1 (binary)Locked or unlocked, nothing else
Core operationswait() and signal(), historically P and Vlock() and unlock()
Who may release itAny thread, including one that never waitedOnly the thread that locked it
Counting variantYes, counts N identical resourcesNo, always binary in effect
Signalling between threadsIts core purposeNot designed for this
Protecting a critical sectionPossible in binary formPurpose-built for this
Priority inheritance supportNot supportedSupported, because of ownership
Typical failure modeA missing or extra signal corrupts the countDeadlock from lock order, or a forgotten unlock
What it is for, in one phraseCounting resources and signallingExclusive access to one section
Role in the bounded-buffer solutionCounts empty and full slotsProtects 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.

A six-slot buffer with three slots filled teal and three empty, a producer adding items on the left and a consumer removing them on the right, with full equals 3 and empty equals 3 labelled above the slots
The bounded buffer: empty counts free slots, full counts filled ones.

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

A mutex tracks which thread locked it, and enforces that only that thread may unlock it. A semaphore tracks no such identity; it is just a counter with two atomic operations. So signal simply increments the counter, with no check on who is calling it.

The two semaphores, empty and full, count available slots and let a thread block until one exists. The mutex protects the shared buffer structure itself, so only one thread edits it at a time. Neither job substitutes for the other, so both are needed at once.

When the buffer is full, the producer locks the mutex, then blocks on empty while still holding that lock. The consumer then blocks too, since it needs the same mutex to free a slot. Neither thread can proceed, so the program deadlocks.

No, it is not, even though both hold only two values. A mutex has ownership: only the locking thread may unlock it. A binary semaphore has no ownership at all. So any thread may signal it, which makes it a weaker guarantee than a mutex.

Frequently Asked Questions

A mutex is a lock with an owner. The thread that locks it is the only one that may unlock it. A semaphore is a counter with no owner, signalled through wait and signal, and any thread may call either operation. That ownership gap is the core difference between them.

Yes, a counting semaphore can start at any non-negative value, such as N for a pool of N resources. A binary semaphore is restricted to 0 or 1 instead. A mutex, by contrast, cannot be initialised above one, since it holds no counter at all.

No, it does not. A mutex holds exactly two states, locked or unlocked, with no numeric value behind either one. That is precisely what separates it from a semaphore, which is built around an integer counter.

Edsger Dijkstra introduced the semaphore, along with its two atomic operations. He named them P, from the Dutch proberen, meaning to test, and V, from verhogen, meaning to increment. Those names still appear in textbooks alongside wait and signal.

wait(), historically P, decrements the semaphore’s counter by one. If the result would fall below zero, the calling thread blocks until another thread calls signal. Once unblocked, the thread proceeds past the wait call.

signal(), historically V, increments the semaphore’s counter by one. If a thread is blocked in wait, one such thread wakes and proceeds. Any thread may call signal, even one that never called wait.

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:


Whatsapp-color Created with Sketch.

By Arun Kumar

Full Stack Developer with a BE in Computer Science, working with React, Next.js, Node.js, MongoDB, and AI/ML tools. Founder of DiffStudy — built to help CS students ace GATE and university exams, and keep developers up to date across AI, cloud, system design, web development, and every field of computer science. Every article is written from real hands-on experience, not just theory.

Leave a Reply

Your email address will not be published. Required fields are marked *


You cannot copy content of this page