A semaphore limits how many goroutines may use a shared resource at once. It holds a fixed number of permits: acquiring consumes one, and releasing returns one. When no permits remain, another acquire waits. This fits resources such as a slow database that accepts only a limited number of simultaneous connections; callers can wait or be told that capacity is full. section 5.2.1 section 5.0
See the permit limit as a gate: only a fixed number of goroutines can pass until one returns a permit. section 5.2.1
Unlike a mutex, which permits one execution at a time, a counting semaphore permits at most N. A semaphore with one permit is a binary semaphore. Although a mutex is the N=1 case for access control, mutex ownership is normally released by its holder; a semaphore permit may be released by another execution. section 5.2.1 section 5.2
A basic semaphore exposes three operations:
- Create it with an initial permit count. section 5.2.1
- Acquire a permit; wait when the count is zero. section 5.2.1
- Release a permit; make one waiting goroutine eligible to continue. section 5.2.2
One implementation stores the remaining permit count and protects it with a mutex through a condition variable. Acquire locks, waits in a loop while permits are unavailable, decrements the count, and unlocks. Release locks, increments the count, signals one waiter, and unlocks. Signaling one waiter matches releasing one permit; broadcasting would wake more goroutines than the newly available permit can serve. section 5.2.2 section 5.2.2
Listing 5.17 Acquire() and Release() functions
\func (rw *Semaphore) Acquire() { rw.cond.L.Lock() #1 for rw.permits <= 0 { rw.cond.Wait() #2 } rw.permits-- #3 rw.cond.L.Unlock() #4 } func (rw *Semaphore) Release() { rw.cond.L.Lock() #5 rw.permits++ #6 rw.cond.Signal() #7 rw.cond.L.Unlock() #8 } #1 Acquires mutex to protect permits variable #2 Waits until there is an available permit #3 Decreases the number of available permits by 1 #4 Releases mutex #5 Acquires mutex to protect permits variable #6 Increases the number of available permits by 1 #7 Signals condition variable that one more permit is available #8 Releases mutex
Inspect the acquire-and-release sequence that guards the permit counter and wakes one blocked goroutine. section 5.2.2
Record completion without missing the signal
Initialize a semaphore with zero permits. A worker calls Release when its work finishes; another goroutine calls Acquire when it needs confirmation. If it arrives first, it waits. If completion happened first, the stored permit lets Acquire return immediately. This avoids the missed-signal problem of signaling a condition variable before any goroutine is waiting. section 5.2.3 section 5.4
Bound a channel buffer
A channel implementation can use a capacity semaphore before adding a message to its queue. Each send acquires capacity, so a full buffer blocks senders. After safely pushing the message under a mutex, the sender releases a permit on a buffer-size semaphore so a waiting receiver can proceed. section 7.2.2 section 7.2.2
Semaphores are also building blocks for larger synchronization abstractions, including channels and waitgroups. They combine concurrency limiting with durable signaling: releases accumulate as permits even when no goroutine is currently waiting. section 1.5.3 section 6.0




