A count with synchronized operations

A semaphore is a synchronization object, not merely an integer that application code reads and writes freely. Its count can be understood as available permits. One successful wait takes a permit, and one post supplies a permit. Checking availability and claiming it must occur as a synchronized operation, not as an unprotected test followed by a separate decrement.

Linux's description of the POSIX interface uses a nonnegative available count. At zero, sem_wait waits until it can decrement the value. Some textbook models instead encode the number of waiters using negative values. That explanatory convention should not be confused with the interface's available-permit count.

References: [1] [2] [3]

Initialization determines the use

To protect a critical section, a semaphore can start with one permit. Every entrant waits before entering and posts once after leaving. With this disciplined pairing, only one successful entrant can remain inside. This is a binary use; initializing a general counting semaphore to one does not automatically prevent a programming error from posting additional permits.

A larger initial count can limit how many operations occupy a resource concurrently. For ordering, the count can start at zero: a worker posts after completing a required stage, while a dependent thread waits. If the post happens first, its permit remains available. If the wait happens first, the caller suspends until a permit can be obtained.

A bounded buffer separates permits from data protection: empty starts at capacity and full at zero, while concurrent updates of buffer indexes also need mutual exclusion. Wait for the appropriate permit before taking that protection. A consumer that instead holds the mutex while blocking because full is zero can prevent a producer from acquiring the mutex to insert data. Individually correct semaphore operations can therefore deadlock when composed in the wrong order.

References: [1] [2] [5]

Why a semaphore is not simply a mutex

Binary use can provide mutual exclusion, but semaphores are not limited to ownership-based locking. In the ordering example, one thread waits and another posts. A mutex instead has an owner that acquired it, and unlocking behavior depends on ownership and the mutex type. Similar-looking pairs of calls do not make the two abstractions universally interchangeable.

Mutex interfaces can also define recursive acquisition, error checking, and recovery after owner death. Choosing a primitive begins with identifying whether the program is protecting shared state, counting resource permits, or announcing completion. A semaphore count does not automatically protect every related data structure; those accesses must follow a complete synchronization protocol.

References: [4] [1]

Waiting, errors, and sharing

POSIX provides nonblocking and timed alternatives. On Linux, sem_trywait reports EAGAIN when no permit can be obtained immediately, while sem_timedwait may report a timeout. A signal handler can interrupt sem_wait. Returning from a wait call therefore does not invariably mean that a permit was acquired: code must check the result before performing the dependent operation.

Semaphores can coordinate processes as well as threads. POSIX distinguishes named and unnamed forms. An unnamed semaphore shared between processes must reside in suitable shared memory. Initialization, the sharing arrangement, and object lifetime are consequently part of the protocol, not incidental details that can be ignored after copying a wait/post pair.

References: [2] [3]