Mutex vs Semaphore
Binary lock vs counting lock
Mutexes and semaphores are both synchronization primitives used to control access to shared resources in concurrent programming. A mutex allows only one thread to access a resource at a time, while a semaphore can allow a configurable number of concurrent accesses.
Mutex
A mutex (mutual exclusion) is a locking mechanism that ensures only one thread can access a shared resource at a time. It has two states: locked and unlocked. A thread acquires (locks) the mutex before accessing the shared resource and releases (unlocks) it when done. If another thread tries to acquire an already-locked mutex, it blocks until the mutex becomes available. Critically, a mutex has ownership — only the thread that locked it can unlock it. This prevents accidental releases by other threads. Mutexes are the go-to tool for protecting critical sections: shared counters, data structures, file handles, or any state that would corrupt if accessed concurrently.
Semaphore
A semaphore is a signaling mechanism that controls access to a shared resource by maintaining a counter. The counter is initialized to a maximum value representing how many threads can access the resource simultaneously. When a thread wants access, it decrements the counter (wait/P operation). When it's done, it increments it (signal/V operation). If the counter reaches zero, subsequent threads block until another thread signals. Unlike mutexes, semaphores have no ownership — any thread can signal a semaphore, not just the one that waited on it. A binary semaphore (counter of 1) looks similar to a mutex but lacks ownership semantics. Counting semaphores are used for resource pools like database connection pools or rate limiters.
Key Differences
- **Concurrency level**: A mutex allows exactly one thread. A semaphore allows up to N threads (configurable). - **Ownership**: A mutex can only be released by the thread that acquired it. A semaphore can be signaled by any thread. - **Purpose**: Mutexes protect critical sections (mutual exclusion). Semaphores manage access to a pool of resources (signaling/counting). - **States**: A mutex has two states (locked/unlocked). A semaphore has a counter that can range from 0 to N. - **Deadlock risk**: Mutexes can deadlock if two threads each hold one mutex and wait for the other. Semaphores can deadlock through similar circular wait patterns. - **Priority inversion**: Mutexes can implement priority inheritance to avoid priority inversion. Semaphores typically cannot.
When to Use Each
**Use a mutex** when exactly one thread should access a resource at a time: updating a shared counter, modifying a shared data structure, writing to a file, or any critical section that requires atomicity. **Use a semaphore** when you need to limit concurrent access to a resource pool: a database connection pool with 10 connections, a rate limiter allowing 5 concurrent API calls, or a producer-consumer queue where you need to signal between threads.
Analogy
**A mutex** is like a single-occupancy bathroom with a lock — one person enters, locks the door, and no one else can enter until they unlock it and leave. Only the person inside can unlock the door. **A semaphore** is like a parking garage with a counter at the entrance showing available spaces. Multiple cars can park simultaneously (up to the max capacity). When the counter hits zero, new cars wait outside. Any car leaving (not just the one that took the last spot) frees up a space.