Questions: The Critical Section Problem and Race Conditions
5 questions to test your understanding
Score: 0 / 5
Question 1 Multiple Choice
Two threads each increment a shared counter 1,000,000 times starting from 0. After both threads finish, the final value is 1,756,432 instead of 2,000,000. What is the most likely explanation?
AThe CPU made arithmetic errors on some additions
BA race condition occurred: the load-modify-store sequence is not atomic, so some increments from one thread overwrote increments from the other
CThe threads ran sequentially, so only one completed its full million iterations
DInteger overflow caused some counts to wrap around
The operation `counter++` compiles to three hardware steps: load the current value, add 1, store the result. If Thread A loads 5, then Thread B also loads 5 before A stores its result, both store 6 — losing one increment. With millions of iterations, these lost updates accumulate to an unpredictable deficit. This is the classic race condition: a result that depends on the interleaving of non-atomic operations.
Question 2 Multiple Choice
A C++ programmer declares a shared counter as volatile to fix a race condition between two threads incrementing it. Will this solve the problem?
AYes — volatile forces all reads and writes to go directly to memory, preventing stale values
BYes — volatile makes operations on the variable atomic
CNo — volatile only prevents the compiler from caching the variable in a register; it does not make the load-modify-store sequence atomic
DNo — volatile makes the problem worse by disabling all compiler optimizations
volatile tells the compiler not to cache the variable in a register and to re-read it from memory on every access. It does NOT make compound operations like `counter++` atomic — the three-step load/modify/store sequence remains interruptible. A thread switch can still occur between the load and the store, allowing another thread to overwrite the value. Proper fixes use atomic types, mutexes, or hardware-supported atomic instructions.
Question 3 True / False
A correct solution to the Critical Section Problem must guarantee mutual exclusion, progress, and bounded waiting — satisfying just two of the three is insufficient.
TTrue
FFalse
Answer: True
All three requirements are necessary. Mutual exclusion prevents two threads from being in the critical section simultaneously. Progress ensures the system doesn't deadlock when threads want to enter. Bounded waiting ensures no thread starves indefinitely. A solution providing mutual exclusion but allowing starvation (violating bounded waiting) is incorrect; so is one preventing deadlock but occasionally allowing simultaneous access (violating mutual exclusion).
Question 4 True / False
Race conditions are easy to detect in testing because they generally produce a clearly wrong result or cause the program to crash.
TTrue
FFalse
Answer: False
Race conditions are notoriously difficult to detect precisely because they manifest intermittently. The outcome depends on the timing of thread interleaving, which varies with CPU load, scheduling decisions, and other environmental factors. A program with a race condition may run correctly thousands of times before the interleaving that exposes the bug occurs — and may never reproduce the bug in a controlled testing environment, even though it fails in production under load.
Question 5 Short Answer
Why does declaring a variable as volatile in C/C++ not fix a race condition on a shared counter?
Think about your answer, then reveal below.
Model answer: volatile prevents the compiler from caching a variable in a register, but it does not make operations on the variable atomic. The increment `counter++` still compiles to three separate hardware steps (load, add, store), and a thread switch between any two steps allows another thread to overwrite the result.
The root cause of the race condition is that counter++ is not a single indivisible operation at the hardware level — it is read-modify-write. volatile only controls compiler behavior (preventing register caching), not the hardware's ability to interleave these steps across threads. Fixing a race condition requires atomicity, which comes from hardware atomic instructions (like compare-and-swap), mutex locks, or std::atomic in C++.