liveBook · Manning

Semaphore

As explained in Learn Concurrent Programming with Go

A synchronization mechanism that controls concurrent access to a resource through a counter of available permits.

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

Figure 5.11 A fixed number of goroutines are allowed to have access.

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:

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

How Learn Concurrent Programming with Go covers it

How deep it goes
Implements permit accounting with condition variables, including Acquire and Release behavior, then applies it to completion and channel buffering.
Examples
Uses a limited database, a zero-permit task-completion signal, and capacity-controlled channel sending.
What it assumes
Introduces semaphore behavior by contrasting it with mutexes and condition variables rather than assuming synchronization-primitive knowledge.
What you can do after
Build it from scratch with a permit counter and condition variable, then apply it to bounded access and completion signaling.
How it connects
Connects mutexes, condition variables, channels, waitgroups, barriers, and readers–writer locks as synchronization tools or abstractions.

Also explained in

Compare how 5 books explain semaphore →

Related concepts

This explainer was generated by Manning's knowledge graph from the text of Learn Concurrent Programming with Go. The section links point to the parts of the book it draws on.