Two threads both execute `counter += 1`. Thread A loads counter=5 and is preempted. Thread B loads 5, increments to 6, and stores. Thread A resumes and stores 6. The final value is 6, not 7. This is best described as:
AA scheduler bug — the OS should never preempt a thread mid-operation
BA hardware bug — CPUs should make read-modify-write atomic by default
CA race condition — the program accesses shared data without synchronization
DA memory allocation bug — both threads should have their own copy of counter
The scheduler is behaving correctly — sharing the CPU is exactly its job, and preemption can happen between any two machine instructions. The bug is in the program: `counter += 1` compiles to three steps (load, add, store), and the program provides no guarantee that these steps run atomically. Race conditions are always program bugs, fixed by synchronization (locks, atomic operations), not by demanding different scheduler behavior.
Question 2 Multiple Choice
After adding a mutex lock around `counter += 1`, a team notices that 8 threads incrementing the counter runs far slower than expected. The most likely explanation is:
AThe mutex is broken — threads are still racing despite the lock
BMutexes have O(n) acquisition cost where n is the number of waiting threads
CLock contention — threads spend significant time blocked waiting for the lock rather than doing useful work
DThe compiler has eliminated the mutex as dead code during optimization
When many threads compete for a single lock protecting a short critical section, most threads spend most of their time blocked, waiting. This is lock contention: the serialization imposed by the lock prevents parallel execution, reducing throughput below even single-threaded performance in extreme cases. The solution is to reduce contention — finer-grained locks, lock-free algorithms, or redesigning to reduce shared state. This is the core tension of concurrent programming: locks prevent races but limit parallelism.
Question 3 True / False
A race condition can primarily occur when two threads run simultaneously on multiple CPU cores; single-core systems are immune.
TTrue
FFalse
Answer: False
Even on a single core, the OS scheduler can preempt a thread between any two machine instructions and switch to another thread. Thread A can be halfway through a read-modify-write when thread B runs and modifies the same data. When A resumes, it operates on a stale value. Preemptive scheduling produces race conditions regardless of the number of cores. Multi-core systems do enable truly simultaneous access, but single-core systems are not safe — they just require a context switch to trigger the race.
Question 4 True / False
Two threads that access shared memory but never write to it simultaneously can safely do so without any synchronization.
TTrue
FFalse
Answer: True
Concurrent reads from shared memory are safe — multiple readers do not interfere with each other because reads do not modify the data. Race conditions require at least one writer. If both threads only read a shared variable, no synchronization is needed. Problems arise when at least one thread writes: a write concurrent with any other access (read or write) creates a data race. The readers-writers problem formalizes this distinction.
Question 5 Short Answer
Explain why `counter += 1` is not an atomic operation even though it appears as a single statement in source code. What must a programmer do to ensure this operation is safe when shared between threads?
Think about your answer, then reveal below.
Model answer: Source-level statements compile to multiple machine instructions. `counter += 1` typically compiles to: (1) load the value of counter into a register, (2) add 1 to the register, (3) store the result back to memory. The scheduler can preempt the thread between any of these steps. To make it safe, the programmer must either use a hardware atomic instruction (like compare-and-swap or atomic increment), or protect the three-step sequence with a mutex so only one thread can execute it at a time.
The gap between source code and machine code is the root of the confusion. High-level languages let programmers reason at a higher abstraction, but the hardware executes sequences of primitive operations with no inherent atomicity guarantee. Synchronization primitives — locks, atomic operations — bridge this gap by providing atomicity guarantees that the source code and hardware do not provide on their own. Understanding this gap is foundational to concurrent programming.