CS Core & Software Engineering · Operating Systems
Synchronization
Races, the critical-section properties, Peterson's two-process lock, semaphores and monitors.
maya still has two threads — UI and save — and one integer, unsaved_count, that starts at 10. If both add 1 without a lock, both can read 10 and both write 11. This lesson is that lost increment, the three properties a lock must keep, Peterson's software lock for exactly two threads, and the semaphore / mutex / monitor tools the kernel actually ships.
- CS Core & Software Engineering
- Hard level
- 4 concepts
- 5 practice questions
1A race on unsaved_count
The UI thread and the save thread of maya both touch a shared integer unsaved_count, currently 10. Each thread wants to add 1. The safe story is: read 10, write 11, then the other reads 11, writes 12. The unsafe story is both read 10 before either writes, both compute 11, both write 11. One increment is lost. That lost update is a race condition: the final value depends on the uncontrolled interleaving.
The lines that touch unsaved_count are the critical section — the code that must not overlap. The next concepts are how to keep two threads from being in that section at once.
Figure. Both threads reach the same variable without ordering; whichever write lands last wins, and the other update is lost.
How it works
- Unsynchronised accessTwo threads both read the same counter, both add one, both write back — one increment vanishes.
- Critical sectionOnly the code that mutates shared data needs protection; the rest of each thread may run freely.
- Three guaranteesMutual exclusion stops overlap; progress stops indefinite blocking when the section is free; bounded waiting stops one lucky thread from starving the rest.
The lost increment
Two threads each run read count, add 1, write count on a shared count of 10, with no locking. What is the smallest possible final value?
- Both read before either writesboth see 10
- Both compute 10 + 1 and writeboth write 11
- Final count11 — one increment lost
Pro tip. The bug is not arithmetic — it is timing. Mutual exclusion forces the read-modify-write to happen as one indivisible sequence.
A solution enforces mutual exclusion but lets one high-priority process keep re-entering the critical section while others wait indefinitely. Which property fails?
- Mutual exclusion
- Progress
- Bounded waiting
Mutual exclusion still holds — only one process is inside at a time. Progress also holds — someone always gets in. Bounded waiting fails because one process never gets a turn.
2Three properties of a correct lock
A lock around unsaved_count is not just 'take turns'. Any correct critical-section solution must satisfy three properties together. Mutual exclusion: at most one thread is in the section. Progress: if the section is free and someone wants in, the choice of who enters cannot be postponed indefinitely. Bounded waiting: a thread that has asked to enter cannot be skipped forever.
A mutex that sometimes lets both threads in fails the first. A mutex that never picks a waiter when the section is free fails the second. A mutex that always favours the UI thread fails the third — the save thread starves.
Write the three names — mutual exclusion, progress, bounded waiting — and ask which one a broken lock violated. There is no diagram of 'progress'.
The three together
- Mutual exclusionUI and save are never both incrementing unsaved_count.
- ProgressIf the count is free, one of the waiters is allowed in.
- Bounded waitingThe save thread cannot be skipped forever.
| Property | If it fails on unsaved_count |
|---|---|
| Mutual exclusion | Both write 11; one increment is lost |
| Progress | The section is free and nobody is let in |
| Bounded waiting | Save waits forever while UI keeps winning |
Which three properties must any correct critical-section solution satisfy?
- Mutual exclusion, progress, bounded waiting
- Mutual exclusion, deadlock freedom, fairness of CPU shares
- Atomic increment, cache coherence, bounded waiting
The textbook trio is mutual exclusion, progress and bounded waiting. Deadlock freedom is a different lesson. Cache coherence is hardware.
3Peterson's two-process lock
Before the machine offered an atomic swap, Peterson's algorithm locked a critical section for exactly two threads using ordinary memory. Call them UI (i) and save (j). Each thread has a flag meaning 'I want in' and they share a turn variable.
UI sets flag[i], yields the turn to save, and spins while save still wants in and still holds the turn. On the way out UI clears flag[i]. The protocol gives mutual exclusion for two participants. It does not generalise to three — a third thread needs a different algorithm.
Figure. Pi declares intent, yields turn to Pj, then spins only while Pj both wants in and holds the turn — software mutual exclusion for exactly two processes.
How it works
- Declare intentProcess i sets flag[i] = true before trying to enter.
- Yield turnProcess i sets turn = j, politely giving the other process priority if both want in.
- Spin gateProcess i waits while flag[j] is true and turn still equals j — the other process both wants in and was given priority.
- ExitAfter the critical section, process i sets flag[i] = false so the other can proceed.
Peterson's algorithm — process i
flag[i] = true;
turn = j; // yield to the other process
while (flag[j] && turn == j)
; // spin
// --- critical section ---
flag[i] = false;Peterson's solution is cited as satisfying all three critical-section properties. How many processes does it correctly synchronise?
- Any number — the flags array generalises
- Exactly two
- Two or three, but not more
The turn variable can favour only one rival at a time. With three or more processes there is no single turn that breaks symmetry, so the algorithm is a two-process construction.
4Semaphores, mutex and monitors
A semaphore is an integer the kernel (or a library) updates atomically. wait, also written P, subtracts one and blocks if the result would be negative. signal, also written V, adds one and wakes a waiter. The integer is the number of permits left.
A counting semaphore tracks a pool — say three identical printer slots. A binary semaphore, used as a mutex, only ever holds 0 or 1 and is released by the same thread that acquired it. A monitor is a different package: the data, the lock, and the condition waits live in one language construct so you do not pair wait/signal by hand.
Figure. Mutex is a lock; semaphore counts permits; a monitor bundles mutual exclusion with condition variables.
How it works
- wait (P)Decrement the semaphore; if the value is now negative, block until another process signals.
- signal (V)Increment the semaphore; if a process was blocked, wake one waiter.
- Mutex disciplineA binary semaphore used as a mutex should be released by the same thread that acquired it.
- Monitor entryEnter the monitor to access shared data; wait on a condition inside if the predicate is false; signal when it becomes true.
| Primitive | Typical use | Release rule |
|---|---|---|
| Counting semaphore | Pool of k identical resources | Any thread may signal |
| Binary semaphore (mutex) | Critical section lock | Same thread that waited |
| Monitor + condition var | Shared structure with a wait predicate | Signal inside the monitor |
Semaphore operations
wait(S):
S = S - 1
if S < 0:
block
signal(S):
S = S + 1
if any process blocked on S:
wake oneA producer thread fills a bounded buffer and a consumer thread empties it. Which tool most directly counts empty slots and full slots?
- One binary mutex only
- Two counting semaphores (empty count and full count) plus a mutex on the buffer
- A monitor with no condition variables
The counts track how many slots are empty and full — a counting-semaphore job. The buffer indices still need a mutex so producer and consumer do not corrupt the same pointer.
Notes
- Critical section problem needs three properties: Mutual Exclusion, Progress, and Bounded Waiting; Peterson's solution satisfies all three for two processes.
- A counting semaphore uses wait/P (decrement) and signal/V (increment); a binary semaphore (mutex) takes values 0 or 1 for mutual exclusion.
- Four Coffman conditions must ALL hold for deadlock: Mutual Exclusion, Hold and Wait, No Preemption, and Circular Wait.
- Banker's algorithm is a deadlock-avoidance method: it grants a request only if the resulting state is 'safe' (a safe sequence exists).
- Monitors provide high-level synchronization with condition variables (wait/signal); unlike semaphores, only one process is active in a monitor at a time.
Formulas
- wait(S): while(S<=0); S--; and signal(S): S++; (P and V operations).
- Deadlock is possible only if all four Coffman conditions hold simultaneously.
- Safe state: there exists an ordering of all processes such that each can finish with currently available + released resources.
- Need[i] = Max[i] - Allocation[i] (resources process i may still request).
- With a single instance per resource type, a cycle in the resource-allocation graph implies deadlock.
Exam traps & shortcuts
- Remember Coffman conditions with 'MHNC': Mutual exclusion, Hold-and-wait, No preemption, Circular wait.
- Breaking any ONE of the four conditions prevents deadlock (prevention); avoidance keeps the system in safe states.
- In a resource graph: single instances -> cycle means deadlock; multiple instances -> cycle only means possible deadlock.
Reference tables
unsaved_count starts at 10. The race writes 11.
| Tool | Job |
|---|---|
| Critical section | The lines that touch unsaved_count |
| Peterson | Software lock for exactly two threads |
| Counting semaphore | A pool of identical permits |
| Mutex / binary semaphore | 0 or 1; same thread releases |
| Monitor | Data + lock + condition waits in one package |
Recap
Two threads, one count, one lost increment unless a lock holds.
- Race
- Both read 10, both write 11. Final value depends on interleaving.
- Three properties
- Mutual exclusion, progress, bounded waiting — together.
- Peterson
- flag plus turn. Two processes only.
- Semaphore
- wait decrements and may block; signal increments and may wake.
- Mutex
- Binary semaphore. The locker unlocks.
Practise Synchronization
Reading is free and needs no account. Practice, mocks and progress live in the app.
- 5 exam-style questions on this topic, with explanations
- A 2-question practice set that ends the chapter
- Timed mocks scored with the real marking scheme
- Readiness tracked per topic, kept on your device