Semaphore
A semaphore is a counter that limits how many threads or tasks may use a resource concurrently by acquiring and releasing permits.
Definition
A semaphore holds a non-negative permit count. Acquiring decrements it and blocks when it would go negative; releasing increments it and wakes a waiter. A binary semaphore acts as a lock, while a counting semaphore caps concurrency at n. Unlike a mutex, a semaphore is not owned — any thread may release a permit — which makes it suitable for signalling between producers and consumers as well as for bounding resources.
Context and scope
Used to cap concurrent access to a bounded resource: database connections, outbound API calls, file handles, or expensive compute slots.
Common misconceptions
- A semaphore is just a mutex — a mutex enforces ownership and one holder; a semaphore counts permits and permits release by any thread
- More permits means more throughput — permits should match the resource capacity; overshooting causes thrashing and timeouts downstream
- Semaphores are obsolete with async — they are the standard way to bound concurrency in async code, where a task waits instead of a thread blocking
Anti-patterns
- Acquiring a permit and forgetting to release it on the error path, permanently shrinking capacity
- Setting the permit count far above the real resource limit, so the limit no longer protects anything
- Using a semaphore as a queue, assuming waiters are served in order when that is unspecified
Related concepts
Related examples
Authoritative source
What it is
A semaphore is an integer counter with two operations. Wait, or acquire, decrements it, blocking if the result would be negative; signal, or release, increments it and wakes a blocked waiter. Initialised to the capacity of a resource, it admits at most that many concurrent users and makes the rest wait. A count of one is a binary semaphore, equivalent to a lock but without the ownership rule.
Why it matters
Unbounded concurrency is how a service exhausts its connections, its memory, or a downstream quota. A semaphore turns an implicit limit into an explicit one at the point of use, so a burst queues at the boundary instead of cascading into failures. In async runtimes where thousands of tasks share a small resource, it is the primitive that keeps concurrency matched to capacity.
How to use it well
Set the permit count to the true capacity of the protected resource and never above it. Always release in a finally-style block so error paths do not leak permits, and use timeouts or bounded waits so a caller cannot hang forever when the resource is saturated. When producers and consumers must rendezvous, a pair of semaphores expresses the handoff cleanly, but do not assume ordered service of waiters unless the implementation guarantees it.