Semaphores — binary and counting, P() and V() operations
WHY do we even need semaphores?
WHAT is a semaphore? A non-negative integer plus two atomic operations. HOW does it work? The value tells you how many threads may still proceed; the operations adjust it safely.
The definition, built from first principles
Deriving P() and V() from what we want
We want to mean: "number of permits currently available."
HOW P() must behave: to take a permit, we must (a) wait until one exists (), then (b) atomically decrement.
In this pure busy-wait form never goes negative — it stays in and simply counts permits. The test () and the decrement must happen as one atomic test-and-decrement, otherwise two threads could both see and both decrement.
HOW V() must behave: to release a permit, just increment (and a waiting thread will see on its next loop).
Binary vs Counting

Worked Example 1 — Mutual exclusion (binary)
Goal: only one thread updates balance at a time.
semaphore mutex = 1 // 1 permit available
Thread:
P(mutex) // Why? grab the only permit; others now block
balance = balance + 100 // critical section — exclusive
V(mutex) // Why? return the permit so someone else can enterWhy start at 1? Because one thread is allowed inside. The first makes mutex = 0; any second thread's sees and blocks until restores it.
Worked Example 2 — Counting (resource pool)
Goal: a printer pool with 3 printers.
semaphore printers = 3
Use a printer:
P(printers) // Why? value 3→2→1→0; 4th requester finds 0 and waits
print_document()
V(printers) // Why? frees one printer, value goes back up, wakes a waiterWhy start at 3? Exactly 3 threads may print simultaneously; the 4th blocks until one finishes.
Worked Example 3 — Ordering with a 0-semaphore
Goal: line B in Thread‑2 must run after line A in Thread‑1.
semaphore done = 0 // no permit yet → forces waiting
Thread-1: Thread-2:
A P(done) // blocks until permit appears
V(done) B // runs only after A's V(done)Why this step? Starting at means must block first. The signal can only come from Thread‑1's , guaranteeing happens-before .
Common Mistakes (Steel-manned)
Flashcards
What is a semaphore?
What does P() do (blocking version)?
What does V() do (blocking version)?
Why must P() and V() be atomic?
Range of a binary semaphore?
What value range can a counting semaphore take?
Initialize a semaphore to enforce mutual exclusion.
Initialize a semaphore for a pool of k identical resources.
Initialize a semaphore for pure ordering / signaling.
If S = -3, what does that mean (blocking semaphore)?
Difference between a binary semaphore and a mutex?
What do P and V stand for?
How can two semaphores cause deadlock?
Consequence of a missing V()?
In a pure busy-wait semaphore, can S go negative?
Recall Feynman: explain it to a 12-year-old
Imagine a small library with 3 reading chairs. At the door is a bowl with 3 tokens. To sit, you must grab a token (that's P); when leaving, you drop your token back (that's V). If the bowl is empty, you wait at the door until someone returns one. The number of tokens left tells everyone how many chairs are free. A binary semaphore is just a library with one chair and one token — only one person inside at a time.
Connections
- Critical Section Problem — the problem semaphores solve.
- Mutex Locks — the owned, exclusive cousin of a binary semaphore.
- Producer-Consumer Problem — classic use of one mutex + two counting semaphores (
empty,full). - Deadlock — caused by bad ordering of P() calls.
- Race Condition — what atomic P()/V() prevents.
- Monitors — a higher-level synchronization construct built on similar ideas.
- Atomic Operations — the hardware foundation that makes P()/V() indivisible.
Concept Map
Hinglish (regional understanding)
Intuition Hinglish mein samjho
Dekho, semaphore ek simple cheez hai: ek integer counter jo OS protect karta hai, aur use sirf do operations se chhoo sakte ho — P() (wait/down) aur V() (signal/up). Isko aise socho ki ek bowl me tokens rakhe hain. Andar jaane ke liye token uthao (P), bahar aate waqt token wapas daal do (V). Token khatam ho gaye to door par wait karo. Counter batata hai kitne permits bache hain.
Binary semaphore matlab sirf 1 token — yaani ek hi banda andar (mutual exclusion, lock jaisa). Counting semaphore matlab N tokens se start — N resources ka pool, jaise 3 printers. Yaad rakho counting semaphore ki value ki koi fixed upper limit nahi hoti — woh kisi bhi non-negative integer tak ja sakti hai; bas hum use initialize resources ki ginti ke barabar karte hain. Aur agar tum semaphore ko 0 se start karo, to woh pure ordering/signaling ke liye kaam karta hai: ek thread P() pe ruk jaata hai jab tak doosra V() na de.
Ek detail jo log galat samajhte hain: pure busy-wait (spinlock) version me semaphore ki value kabhi negative nahi hoti — wahan loop "while S==0" hota hai aur decrement tabhi hota hai jab S>0 ho. Negative value sirf us blocking design me aati hai jahan OS process ko sula deta hai, aur tab |S| batata hai kitne processes wait kar rahe hain. P() aur V() atomic hone chahiye — agar tum khud "value padho phir ghatao" karoge to beech me context switch aa ke wahi race condition wapas aa jaayegi.
Do common galtiyan yaad rakho: (1) binary semaphore aur mutex same nahi hain — mutex me ownership hota hai, semaphore me nahi. (2) Do alag locks ko ulte order me lene se deadlock ho jaata hai, isliye hamesha ek fixed order me P() karo. Aur har P() ke saath ek V() zaroor karo, warna permit leak ho ke baaki threads forever block ho jaayenge.