Mutex vs Semaphore
Scenario
A mutex is a bathroom key: only one person gets it, and they must return it. A semaphore is a parking lot with 5 spaces: the attendant lets 5 cars in, and anyone can leave to free up a spot.
Mental model
A mutex is about exclusive ownership (locking), while a semaphore is about counting available resources (signaling).
Mutexes protect a single resource by ensuring only one thread accesses it at a time. Semaphores track a pool of identical resources, allowing multiple threads up to a strict limit.
Explanation
When dealing with concurrency, we need practical tools to prevent race conditions. The two most fundamental synchronization primitives provided by operating systems are the mutex and the semaphore. While they are often confused, they serve completely distinct architectural purposes: a mutex is used for locking, and a semaphore is used for signaling and counting.
A Mutex (short for Mutual Exclusion) is a binary lock. When a thread wants to enter a critical section, it acquires the mutex. If another thread already holds it, the new thread is blocked and goes to sleep until the mutex is released. The most critical defining feature of a mutex is ownership: the thread that locks a mutex is the exact same thread that must unlock it. You cannot have Thread A lock a mutex and Thread B unlock it. This strict ownership makes mutexes the perfect tool for protecting shared data like linked lists or banking balances from concurrent modifications.
A Semaphore, invented by Edsger Dijkstra, is essentially an integer counter with two atomic operations: 'wait' (decrements) and 'signal' (increments). If the counter is greater than zero, a thread can decrement it and proceed. If the counter is zero, the thread blocks. Unlike a mutex, a semaphore has absolutely no concept of ownership. Any thread can signal (increment) a semaphore. This makes semaphores incredibly powerful for signaling between threads (e.g., Thread A finishes a task and signals Thread B to wake up) and managing pools of resources, like a connection pool that allows exactly 10 concurrent database connections.
A common point of confusion is the 'Binary Semaphore'—a semaphore initialized to 1. Functionally, a binary semaphore acts like a mutex because it restricts access to one thread at a time. However, even a binary semaphore lacks the ownership requirement of a true mutex. Because operating systems know who owns a mutex, they can provide advanced protections like Priority Inheritance (temporarily boosting the priority of a thread holding a lock to prevent priority inversion), which cannot be done with semaphores.
In system design, use a mutex when you need to protect shared state from corruption. Use a semaphore when you need to track how many items are available or when you need one thread to notify another thread that an event has occurred.
Key points
- Ownership: A mutex must be unlocked by the thread that locked it. Semaphores can be signaled by any thread.
- Purpose: Mutexes are for mutual exclusion (protecting data). Semaphores are for counting resources or signaling between threads.
- Priority Inversion: Mutexes can implement priority inheritance to solve priority inversion; semaphores generally cannot.
Common mistakes
- Using a binary semaphore as a direct substitute for a mutex: While they behave similarly, binary semaphores lack ownership tracking, making them harder to debug and incapable of preventing priority inversion.
- Thinking semaphores protect data: Semaphores track quantities. If a counting semaphore allows 3 threads in, those 3 threads can still corrupt shared data unless a mutex is also used inside the critical section.
Glossary
- synchronization primitives
- The fundamental, built-in tools provided by an operating system to help programmers safely manage programs that run multiple tasks at the exact same time.
- mutex
- A digital lock used to guarantee that only one single thread can access a specific piece of shared data at a time, preventing messy collisions.
- semaphore
- A digital signaling mechanism, like a bouncer with a clicker, used to limit how many threads can access a shared resource (like a pool of database connections) simultaneously.
Recall questions
- What is the primary difference in ownership between a mutex and a semaphore?
- What are the two fundamental operations on a semaphore?
- Why is a mutex better suited to solve priority inversion than a binary semaphore?
Questions & answers
Which synchronization primitive is best suited for managing a pool of 10 identical database connections?
A counting semaphore.
Approach: Initialize the semaphore to 10. Threads 'wait' to take a connection and 'signal' when returning it, naturally limiting concurrency to 10.
Can Thread B unlock a mutex that was locked by Thread A?
No.
Approach: Mutexes have strict thread ownership. Attempting to unlock another thread's mutex leads to undefined behavior or OS-level errors.
Continue learning
Previous: Race Condition & Critical Section
Next: Producer-Consumer Problem