๐Ÿšฆ Full Lesson ยท Operating Systems
Counting Permits That Control Access to a Shared Resource
Semaphores and Synchronization

When multiple processes or threads share the same resource, something has to enforce the rules of taking turns โ€” a semaphore is exactly that: a simple counter with two atomic operations that prevent chaos when many things want the same thing at once.

The Core Idea
Coordinating Access to Shared Resources

A semaphore is a synchronization tool consisting of a counter and two operations โ€” traditionally called wait (or P, decrements the counter) and signal (or V, increments the counter) โ€” used to control how many processes or threads can access a shared resource simultaneously. The critical property is that both operations are atomic โ€” they happen as a single, indivisible unit, with no possibility of another process interrupting halfway through and causing an inconsistent state.

This solves the fundamental problem synchronization exists to address: without some coordination mechanism, multiple threads or processes reading and writing the same shared data simultaneously can produce a race condition โ€” an unpredictable, timing-dependent bug where the final result depends on the precise, uncontrolled order in which different threads happen to execute, often producing subtly wrong results that are notoriously difficult to reproduce and debug.

๐Ÿ’ก Memory Trick
Picture a parking garage with a fixed number of available spots and a physical sign at the entrance showing exactly how many spots remain. Every time a car enters (wait/P), the sign's count is decremented by one โ€” and if the count is already at zero, incoming cars must wait outside until a spot opens up. Every time a car leaves (signal/V), the count is incremented by one, potentially allowing a waiting car to enter. A BINARY semaphore is a garage with exactly one spot (a simple lock โ€” either occupied or free), while a COUNTING semaphore is a garage with several spots, allowing a specific limited number of cars in simultaneously.
The Two Semaphore Types
Binary vs. Counting Semaphores
1
Binary Semaphore (Mutex-like)
A semaphore whose counter can only ever be 0 or 1, functioning essentially as a simple lock: only one process/thread can hold it at a time, forcing strictly exclusive access to whatever resource it protects.
2
Counting Semaphore
A semaphore whose counter can range over multiple values, allowing up to N processes/threads to access a shared resource (or resource pool) simultaneously, where N is the semaphore's initial count โ€” useful for managing a limited pool of interchangeable resources, like a fixed number of available database connections.
The Critical Section Problem
Why This Coordination Is Necessary

A critical section is a section of code that accesses shared data in a way that could produce incorrect results if multiple threads execute it simultaneously without coordination โ€” like two threads both reading a shared counter's current value, both independently incrementing that value, and both writing back, potentially causing one of the two increments to be silently lost if the timing overlaps just wrong. Semaphores solve this by wrapping the critical section between a wait and a signal operation, ensuring only the permitted number of threads can be inside the critical section at any given moment.

Getting this coordination wrong doesn't just risk race conditions โ€” it can also introduce the Deadlock conditions covered in an earlier lesson (if semaphores are acquired in an inconsistent order across different threads) or 'starvation' (if a particular thread is repeatedly denied access to a semaphore while others continually cut ahead) โ€” synchronization mechanisms solve one class of problem (race conditions) but must themselves be used carefully to avoid introducing these other, equally serious classes of problems.

๐Ÿ–ฅ๏ธ Applied Scenario
A web application's shared inventory counter, tracking how many units of a popular product remain in stock, occasionally shows an incorrect (too-high) count after a flash sale with many simultaneous purchases.
1
You identify this as a classic race condition: two purchase requests both read the current inventory count (say, 5) at nearly the same instant, both independently calculate '5 minus 1 = 4,' and both write back 4 โ€” even though two units were actually purchased, meaning the count should now correctly read 3.
2
You wrap the 'read count, decrement, write count' sequence in a binary semaphore (acting as a mutex), ensuring only one purchase request's inventory-update code can execute that critical section at a time.
3
With the semaphore in place, the second purchase request must wait until the first one has fully completed its read-decrement-write sequence before it can begin its own, guaranteeing it reads the ALREADY-updated count rather than a stale one.
4
Conclusion: the race condition is eliminated, since the semaphore guarantees the critical section (reading, decrementing, and writing the shared inventory count) is never executed by more than one request simultaneously, regardless of how many purchase requests arrive at nearly the same instant.
๐Ÿ“Œ Exam Application
Exam questions frequently present a scenario involving shared data accessed by multiple threads and ask you to identify whether a race condition could occur, and to propose a synchronization mechanism (semaphore or mutex) to prevent it. You may also be asked to distinguish binary semaphores from counting semaphores and identify which is appropriate for a described resource-sharing scenario.
โš ๏ธ Most Common Semaphores and Synchronization Mistakes
The most common mistake is assuming a race condition requires threads to run at the EXACT same instant โ€” in reality, a race condition can be triggered by any interleaving of the threads' individual read/modify/write steps, even ones separated by very brief timing gaps, since the shared data's final state depends on the specific, non-deterministic order those steps happen to interleave in. Another frequent error is using a counting semaphore where a binary semaphore (mutex) was actually needed, or vice versa โ€” a counting semaphore initialized to a value greater than 1 will incorrectly allow multiple threads into a section of code that actually requires strictly exclusive, one-at-a-time access.
โœ“ Quick Self-Test
Can you explain, using a concrete example, what a race condition is and why it produces unpredictable results? Can you explain the difference between a binary semaphore and a counting semaphore, and identify which one is appropriate for a described resource-sharing scenario?
Next Lesson
Paging
โ†’
โ† All Operating Systems Lessons