A counting semaphore is initialized to 3 to govern access to a pool of 3 printers. Two threads each call wait(). A third thread then calls wait(). Now a fourth thread calls wait(). What happens to the fourth thread?
AIt proceeds — the semaphore allows one more thread since it can go to -1
BIt blocks — the semaphore is at 0, indicating no resources are available
CIt causes a deadlock — too many threads called wait()
DIt returns immediately with an error indicating the pool is full
After threads 1, 2, and 3 each call wait(), the semaphore is decremented from 3 to 0. When the fourth thread calls wait(), it finds the count is 0 — no resources remain — so it blocks. The blocked thread is queued and will be woken when one of the three earlier threads calls signal() to return a printer to the pool. The semaphore value never goes below zero; blocked threads are maintained in a queue, not reflected in the count.
Question 2 Multiple Choice
In a bounded-buffer producer-consumer solution using two counting semaphores (empty=N, full=0), why is a mutex also necessary?
AIt is not necessary — the two counting semaphores already prevent all races
BThe mutex ensures that only one thread runs at a time, which the semaphores cannot do
CThe counting semaphores manage capacity (how many slots), but the buffer data structure itself needs mutual exclusion to prevent concurrent reads and writes from corrupting it
DThe mutex initializes the semaphore values correctly before threads begin
The two counting semaphores handle capacity: empty counts available empty slots, full counts filled slots. They prevent a producer from overwriting a full buffer and a consumer from reading from an empty one. However, they do NOT protect the buffer data structure from concurrent modification. If two producers simultaneously find empty > 0, both call wait(empty), and both write to the buffer, they can write to the same slot, corrupting data. A mutex around the actual read/write operation prevents this — a clean separation of concerns: semaphores manage capacity, mutex protects shared state.
Question 3 True / False
A counting semaphore's internal value can go below zero, with the absolute value representing the number of threads currently blocked waiting for a resource.
TTrue
FFalse
Answer: False
By definition, a counting semaphore's value never goes below zero. When a thread calls wait() on a semaphore at 0, the thread is placed in a waiting queue and blocked — but the semaphore's count stays at 0, not -1. Some textbook formulations do define semaphores with negative values to encode queue length, but the standard implementation maintains a non-negative count alongside a separate queue. The practical consequence is that the count always accurately reflects the number of currently available resources.
Question 4 True / False
A binary semaphore (mutex) is a special case of a counting semaphore initialized to 1.
TTrue
FFalse
Answer: True
A counting semaphore initialized to 1 behaves identically to a binary semaphore or mutex: only one thread can hold it at a time (the count drops from 1 to 0), and any other thread calling wait() blocks until the holder calls signal(). The generalization is clear: a counting semaphore with initial value N allows up to N threads to proceed concurrently. Setting N=1 enforces mutual exclusion, which is exactly what a mutex does. Counting semaphores thus generalize binary semaphores from 'exactly 1 allowed' to 'at most N allowed.'
Question 5 Short Answer
Explain why a mutex (binary semaphore) would be inadequate to manage a pool of 10 identical database connections, and how a counting semaphore solves this naturally.
Think about your answer, then reveal below.
Model answer: A mutex only allows one thread at a time — it enforces mutual exclusion. But a connection pool with 10 connections should allow up to 10 threads to use connections simultaneously. Using a mutex means 9 connections sit idle even when demand exists, creating unnecessary bottlenecks. A counting semaphore initialized to 10 solves this by encoding the number of available resources as an integer: each wait() claims one connection (decrement), each signal() returns one (increment). Up to 10 threads can hold the semaphore concurrently (value 0 means all are in use); the 11th blocks until one is returned. The semaphore value directly represents available capacity, making the constraint both natural and self-enforcing.
The key insight is that a counting semaphore is not just 'a mutex with a bigger number' — it models a fundamentally different concept: a pool of identical resources rather than a single exclusive critical section. The count IS the resource count. This makes the code's intent immediately readable: semaphore initialized to N means N resources available, and the semaphore tracks that count automatically through all concurrent wait() and signal() calls.