Unit 6 · Self-check
Unit 6 Self-Check Quiz
Twenty-five questions covering race conditions, the critical-section problem, locks, semaphores, monitors, condition variables, and synchronization in Linux. Each question can be expanded to reveal a model answer.
Questions
Multiple choice and short answer
Answer each question before expanding. These span all seven lessons of Unit 6.
Question 1
What is a race condition, and what is the minimum condition under which one can occur?
Show answer
counter++ performed by two threads — the read-modify-write sequence can interleave and lose an update.
Question 2
Which of the following is not a requirement of a correct critical-section solution?
Show answer
Question 3
Explain the difference between mutual exclusion, progress, and bounded waiting with a short example of a solution that satisfies two but not the third.
Show answer
Question 4
What is the essential idea of Peterson's algorithm, and why does it work?
Show answer
flag array (one per thread) and a turn variable. To enter the critical section, a thread sets its flag to true and sets turn to the other thread's ID. It then waits until either the other thread's flag is false or the turn is its own. The combination is the key: a thread expresses both "I want to enter" (flag) and "I defer to the other" (turn). Mutual exclusion follows because the last writer of turn defers to the other; if both are inside, both would have to have set turn to each other, which is impossible. Progress and bounded waiting follow from the turn variable, which guarantees the waiting thread gets its chance on the next round. Peterson's algorithm is elegant but assumes sequential consistency; on modern hardware with relaxed memory models, memory fences are required, and the algorithm is not used in practice.
Question 5
Which hardware instruction is commonly used to implement a spinlock, and what does it do?
Show answer
TEST-AND-SET (or its equivalent, XCHG, LDREX/STREX, CMPXCHG) atomically reads the current value of a memory location and writes a new value (typically 1). The read and write cannot be interleaved with another core's access. A spinlock uses it like this: loop calling test-and-set; when it returns 0 (the lock was free), the caller has acquired the lock. Release is a simple store of 0. These instructions are the primitive on which mutexes, semaphores, and higher-level primitives are built.
Question 6
What is the difference between a spinlock and a mutex, and when would you prefer each?
Show answer
Question 7
What are the two atomic operations on a semaphore, and what does each do?
Show answer
sem_wait in POSIX) and signal (also called V, from verhogen, "to increment", or sem_post). Wait decrements the semaphore's value; if the value would become negative, the calling thread blocks until it can complete the decrement. Signal increments the value; if there are blocked waiters, one is woken. Both operations are atomic — the kernel or hardware guarantees that no other thread can observe an intermediate state.
Question 8
What is the difference between a binary semaphore and a counting semaphore?
Show answer
Question 9
In the producer-consumer problem with a bounded buffer of size B, what should the initial values of the three semaphores be?
Show answer
empty = B) and zero filled slots (full = 0). The mutex is a binary semaphore protecting the buffer indices, so it starts at 1 (unlocked). Producers wait on empty before inserting and signal full after; consumers wait on full before removing and signal empty after. The critical section for the buffer modification is protected by mutex.
Question 10
What is a monitor, and what problem does it solve that semaphores leave to the programmer?
Show answer
lock statement). The monitor solves the problem that with raw semaphores, the burden of correct placement — where to call wait and signal, in what order, with what initial values — is entirely on the programmer, and mistakes produce deadlocks or races that are hard to debug. The monitor's mutual exclusion is automatic: entering any monitor method implicitly acquires the monitor lock, and leaving it releases it. Condition variables inside the monitor provide waiting and signalling, again with the lock handled by the monitor.
Question 11
What is the difference between signal and broadcast on a condition variable, and when should each be used?
Show answer
signal wakes one waiting thread; broadcast wakes all of them. Use signal when the state change satisfies at most one waiter's predicate — for example, a producer inserts one item, so at most one consumer can now proceed. Use broadcast when the state change might satisfy several waiters, or when the predicate is complex and you cannot be sure which waiter is the right one. For example, when a reader-writer lock is released, whether a reader or a writer can proceed depends on the policy; broadcast is safe because each woken thread re-checks its own predicate. The cost of broadcast is a thundering-herd effect: many threads wake, contend for the lock, and all but one find the predicate still false and go back to sleep. Signal is more efficient when its correctness condition holds.
Question 12
Why must pthread_cond_wait be called inside a while loop rather than an if?
Show answer
if, a spurious wakeup would lead the thread to proceed as if the predicate were true. Second, even a legitimate signal may not select the right thread: if multiple threads wait and a signal is delivered to an arbitrary one, the woken thread may find that another thread has already consumed the condition (a "stolen wakeup"). The while loop re-checks the predicate after every wakeup, so the thread only proceeds when the predicate is genuinely true. The general rule: the condition variable must always be used with a predicate, and the predicate must be re-checked after the wait.
Question 13
In the semaphore-based producer-consumer, why must the mutex-protected insert/remove happen between the semaphore operations, not before or after them?
Show answer
empty and full control how many slots are free and how many are filled. The mutex protects the buffer indices and the slot array. If the producer acquires the mutex first and then blocks on empty (because the buffer is full), the consumer cannot proceed: it needs the mutex to remove an item, but the producer holds it while waiting for the consumer to remove an item. Deadlock. If the operations are inverted (post the "filled" semaphore before inserting the item), a consumer may wake and remove a slot that has not yet been written — reading garbage. The rule is: wait on the "resource available" semaphore before entering the critical section, and post the "resource produced" semaphore after leaving it.
Question 14
Explain the difference between the readers-writers problem with reader preference and with writer preference. Which can starve, and why?
Show answer
rwlock is reader-preferring by default; the rwsem is writer-preferring.
Question 15
What is the difference between a futex and a traditional mutex in Linux?
Show answer
futex system call to put itself on the kernel's wait queue. This is dramatically faster than a traditional mutex, whose lock and unlock always involve a system call. POSIX mutexes on Linux are implemented on top of futexes, so the distinction is mostly one of implementation: the programmer sees pthread_mutex_lock, and the cost in the uncontended case is a single atomic operation.
Question 16
What is a "thundering herd", and how does it arise with condition variables?
Show answer
broadcast is used: every waiting thread wakes, fights for the mutex, re-checks the predicate, and all but one find it still false and go back to sleep. The result is wasted CPU and increased latency. It is avoided by using signal where only one waiter can be satisfied, by partitioning waiters across multiple condition variables (so that a signal targets only those whose predicate could have changed), or by adopting a design where the predicate is satisfied for exactly as many waiters as are woken.
Question 17
In the dining philosophers problem, why does the naive solution (everyone picks up the left fork first) deadlock?
Show answer
Question 18
Which of the following is a correct use of a condition variable?
Show answer
while loop (spurious wakeups and stolen wakeups), (c) calling pthread_cond_wait, which atomically releases the mutex and blocks, and (d) re-checking the predicate on every wakeup. The first choice is missing the loop; the third calls wait without the mutex (undefined behaviour); the fourth uses if instead of while. Signal and broadcast also require holding the mutex, though the reason is subtle — it guarantees that the signaller's predicate change is visible atomically to the waiter.
Question 19
What is the difference between voluntary and involuntary context switching, and how does this relate to synchronization?
Show answer
pthread_mutex_lock when the lock is held, on pthread_cond_wait, on sem_wait, or on I/O. The thread chose to give up the CPU. An involuntary context switch happens when the scheduler preempts a running thread — for example, on a timer tick or when a higher-priority thread becomes ready. Synchronization primitives cause voluntary switches when a thread must wait, so an application that blocks frequently on locks or condition variables will show a high voluntary-switch count. A high involuntary count in a synchronised program usually means CPU contention, not lock contention. Tools like perf and /proc/<pid>/status expose both counts, which makes it possible to distinguish the two problems.
Question 20
What is the difference between a deadlock, a livelock, and starvation?
Show answer
Question 21
In Linux, what is RCU (Read-Copy-Update), and for what workload is it best suited?
Show answer
Question 22
Why does the naive spinlock (busy-waiting on test-and-set) scale poorly on a multicore system with many cores contending for the lock?
Show answer
Question 23
What is the difference between a mutex and a semaphore from a correctness standpoint? Which is more error-prone, and why?
Show answer
Question 24
What is the "lost wakeup" problem, and how does condition variable design prevent it?
Show answer
pthread_cond_wait, and requiring the signaller to hold the mutex while changing the predicate and while calling pthread_cond_signal. This mutual exclusion ensures that either the signaller runs first and changes the predicate before the waiter checks it (so the waiter sees the new predicate and does not wait), or the waiter runs first and is already waiting when the signaller signals (so the signal is not lost). Semaphores avoid the same problem by counting: a sem_post with no waiter simply increments the count, so the corresponding sem_wait will not block.
Question 25
In one or two sentences, state the central design principle that distinguishes a well-synchronised concurrent program from a poorly synchronised one.
Show answer
Ready for the graded quiz?
Once you are comfortable with every question above, take the graded Unit 6 quiz in the LMS. It covers the same material with multiple-choice questions, short-answer questions about synchronization design, and a worked interleaving problem.