Loading learning content...
Understanding how Peterson's Solution works is one thing. Proving that it works under all possible interleavings is another. Concurrent algorithms are notoriously difficult to verify because the number of possible execution orderings grows exponentially with the number of operations.
In this page, we undertake the rigorous task of proving Peterson's Solution correct. We will demonstrate that no matter how the scheduler interleaves the operations of Process 0 and Process 1, the algorithm guarantees:
By the end of this page, you will be able to construct formal proofs for concurrent algorithms, understand invariants and their role in correctness proofs, apply proof by contradiction to mutual exclusion, and analyze worst-case behavior for bounded waiting.
Before we begin the proofs, we must establish the system model and assumptions under which Peterson's Solution operates. The proofs are only valid within this model.
The System Model:
The Algorithm for Reference:
12345678910111213141516171819202122232425262728
// Shared variablesbool flag[2] = {false, false};int turn; // 0 or 1 // Process i (where i ∈ {0, 1})void process(int i) { int j = 1 - i; // The other process while (true) { // L0: Non-critical section // L1: flag[i] = true; Entry section - step 1 // L2: turn = j; Entry section - step 2 // L3: while (flag[j] && turn == j) { } // Entry section - step 3 // L4: Critical Section // L5: flag[i] = false; Exit section }} // Line labels for proof references:// L0 = Non-critical section// L1 = Set flag[i] = true// L2 = Set turn = j// L3 = While loop check// L4 = Critical section// L5 = Set flag[i] = false (exit)We'll use Pi to denote Process i (P0 or P1) and Pj to denote the other process (j = 1-i). When we say 'Pi is at Ln', we mean Process i's program counter is at line Ln. 'Pi is in CS' means Pi is executing its critical section (L4).
Theorem 1 (Mutual Exclusion): At any time, at most one process is in the critical section.
Proof Strategy: We prove this by contradiction. We assume both processes are in the critical section simultaneously and derive a contradiction with the algorithm's logic.
Proof:
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475
════════════════════════════════════════════════════════════════════════THEOREM 1: MUTUAL EXCLUSION════════════════════════════════════════════════════════════════════════ CLAIM: At most one process can be in the critical section at any time. PROOF BY CONTRADICTION:─────────────────────────────────────────────────────────────────────── Assume, for contradiction, that at some time T: - P0 is in the critical section (at L4), AND - P1 is in the critical section (at L4) For P0 to be in CS: - P0 must have executed L1 (flag[0] = true) - P0 must have executed L2 (turn = 1) - P0 must have passed L3 (the while loop exited) For the while loop to exit for P0: - The condition (flag[1] && turn == 1) was false - This means: flag[1] == false OR turn != 1 (i.e., turn == 0) Similarly, for P1 to be in CS: - P1 must have executed L1 (flag[1] = true) - P1 must have executed L2 (turn = 0) - P1 must have passed L3 (the while loop exited) For the while loop to exit for P1: - The condition (flag[0] && turn == 0) was false - This means: flag[0] == false OR turn != 0 (i.e., turn == 1) ───────────────────────────────────────────────────────────────────────NOW, LET'S ANALYZE WHEN EACH PROCESS LAST SET 'turn':─────────────────────────────────────────────────────────────────────── Let: - T0_turn = the time when P0 executed 'turn = 1' (L2) - T1_turn = the time when P1 executed 'turn = 0' (L2) These are the last times each process set 'turn' before entering CS.One of these must have happened LAST. Consider two cases: CASE 1: P0 set turn last (T0_turn > T1_turn)─────────────────────────────────────────────- P0 executed 'turn = 1' AFTER P1 executed 'turn = 0'- So the final value of turn is 1 (P0's write won)- When P0 checked its while condition, turn == 1- P0's condition: flag[1] && turn == 1 - Since P1 already set flag[1] = true (before setting turn = 0, which was before P0's turn = 1), we have flag[1] == true- So P0's condition is: true && true = true- P0 should be WAITING, not in CS! CONTRADICTION! CASE 2: P1 set turn last (T1_turn > T0_turn)─────────────────────────────────────────────- P1 executed 'turn = 0' AFTER P0 executed 'turn = 1'- So the final value of turn is 0 (P1's write won)- When P1 checked its while condition, turn == 0- P1's condition: flag[0] && turn == 0- Since P0 already set flag[0] = true (before setting turn = 1, which was before P1's turn = 0), we have flag[0] == true- So P1's condition is: true && true = true- P1 should be WAITING, not in CS! CONTRADICTION! ───────────────────────────────────────────────────────────────────────CONCLUSION:───────────────────────────────────────────────────────────────────────Both cases lead to a contradiction. Therefore, our assumption that bothprocesses are in the critical section simultaneously must be false. ∴ At most one process can be in the critical section at any time. ∎The proof hinges on a crucial observation: the turn variable can only hold ONE value at a time. When both processes compete, the last one to write to turn is the one that waits. Since flag-setting precedes turn-setting, both flags are true before either process can enter, and turn determines the winner.
Theorem 2 (Progress): If no process is in the critical section and some processes wish to enter, then the selection of which process enters the critical section cannot be postponed indefinitely.
Formal Statement: If no process is at L4 (critical section), and at least one process is at L1, L2, or L3 (entry section), then eventually some process will enter the critical section.
Proof:
1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768
════════════════════════════════════════════════════════════════════════THEOREM 2: PROGRESS════════════════════════════════════════════════════════════════════════ CLAIM: If the critical section is empty and one or more processes want to enter, some process will enter in finite time. PROOF:─────────────────────────────────────────────────────────────────────── Consider the case where no process is in CS and at least one process(say P0) wants to enter (is in its entry section). CASE 1: Only P0 wants to enter (P1 is at L0 with flag[1] = false)─────────────────────────────────────────────────────────────────────- P0 executes L1: flag[0] = true- P0 executes L2: turn = 1- P0 checks L3: while (flag[1] && turn == 1)- Since P1 is at L0, flag[1] = false- Condition: false && true = false- P0 immediately exits the while loop- P0 enters the critical section (L4) PROGRESS ACHIEVED: P0 enters in finite time (three steps). CASE 2: Only P1 wants to enter (P0 is at L0 with flag[0] = false)─────────────────────────────────────────────────────────────────────- By symmetric argument to Case 1, P1 enters in finite time. PROGRESS ACHIEVED. CASE 3: Both P0 and P1 want to enter (both in entry section)─────────────────────────────────────────────────────────────────────- Both processes will execute L1 and L2.- After both have executed L2, turn has some value (0 or 1).- Let's say turn = 0 (P1's write was last, or P1 was slower). Sub-case 3a: turn = 0- P0: checks (flag[1] && turn == 1) → (true && false) → false → ENTERS!- P1: checks (flag[0] && turn == 0) → (true && true) → WAITS P0 enters in finite time. Sub-case 3b: turn = 1- P0: checks (flag[1] && turn == 1) → (true && true) → WAITS- P1: checks (flag[0] && turn == 0) → (true && false) → false → ENTERS! P1 enters in finite time. In either sub-case, exactly one process enters immediately. PROGRESS ACHIEVED. ───────────────────────────────────────────────────────────────────────IMPORTANT OBSERVATIONS:─────────────────────────────────────────────────────────────────────── 1. A process NOT in the entry section (at L0 with flag = false) cannot affect the decision. Only processes with flag = true participate. 2. The turn variable ensures that when both flags are true, exactly one process proceeds while the other waits. 3. No indefinite postponement is possible because: - If only one process wants entry, it enters immediately. - If both want entry, turn determines one enters immediately. ∴ Progress is guaranteed. ∎The progress property ensures that when the critical section is free and someone wants it, we don't get stuck deciding who enters. It does NOT guarantee how long waiting takes—that's bounded waiting. Progress only guarantees that SOME process enters, not which one or how fast.
Theorem 3 (Bounded Waiting): Once a process has expressed its desire to enter the critical section, there is a bound on the number of times other processes can enter the critical section before this process enters.
Claim: In Peterson's Solution, the bound is exactly 1. That is, if Pi starts its entry section and Pj is already in or enters the critical section, Pi will enter after at most one critical section execution by Pj.
Proof:
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108
════════════════════════════════════════════════════════════════════════THEOREM 3: BOUNDED WAITING (Bound = 1)════════════════════════════════════════════════════════════════════════ CLAIM: Once Pi begins its entry section, Pj can enter the critical section at most once before Pi enters. PROOF:─────────────────────────────────────────────────────────────────────── Let Pi start its entry section at time T_start.At T_start, Pi executes: L1: flag[i] = true L2: turn = j L3: while (flag[j] && turn == j) { /* wait */ } We analyze what happens regarding Pj's potential entries. SCENARIO 1: Pj is not interested (flag[j] = false)─────────────────────────────────────────────────────────────────────- Pi's while condition: false && (anything) = false- Pi enters immediately without waiting.- Bound satisfied: Pj entered 0 times (trivially ≤ 1). SCENARIO 2: Pj is interested or in CS (flag[j] = true)─────────────────────────────────────────────────────────────────────- Pi must wait in the while loop until one of: a) flag[j] becomes false, OR b) turn becomes i (turn != j) Let's trace Pj's possible actions: Case 2a: Pj is currently in CS at time T_start- Pj will eventually exit and execute L5: flag[j] = false- When flag[j] becomes false, Pi's condition becomes false- Pi enters immediately.- Bound: Pj entered 1 time (the current execution). ✓ Case 2b: Pj is in its entry section at time T_start- Pj will eventually pass L3 (the while loop)- When Pj exits loop, Pj enters CS, then exits (flag[j] = false)- Pi can then enter.- Bound: Pj entered 1 time. ✓ BUT WAIT: What if Pj immediately tries to enter again?───────────────────────────────────────────────────────────────────── Suppose Pj exits CS (sets flag[j] = false) and IMMEDIATELY starts a new entry attempt: 1. Pj at L5: flag[j] = false // Pi might exit its while loop here!2. Pj at L1: flag[j] = true // Pj's new attempt3. Pj at L2: turn = i // ← THIS IS KEY!4. Pj at L3: while (flag[i] && turn == i) Now analyze both processes' while conditions: Pi's condition (entered before Pj's new attempt, still waiting): - flag[j] = true (Pj just set it in step 2) - turn = i (Pj just set it in step 3) - Condition: true && false = FALSE → Pi EXITS LOOP! Pj's condition: - flag[i] = true (Pi set it at T_start, still waiting) - turn = i (Pj set it in step 3) - Condition: true && true = TRUE → Pj WAITS! The key insight: When Pj re-enters, it sets turn = i.This favors Pi over Pj, releasing Pi from its wait. ───────────────────────────────────────────────────────────────────────FORMAL ARGUMENT:─────────────────────────────────────────────────────────────────────── Suppose Pi is waiting (in while loop at L3).For Pj to enter CS (again), Pj must: 1. Execute L1: flag[j] = true 2. Execute L2: turn = i ← Sets turn to favor Pi! 3. Pass L3: while (flag[i] && turn == i) After step 2, turn = i. Pi's while condition is: flag[j] && turn == j = true && (turn == j) = true && false (because turn = i, which is not j) = false So Pi's while loop terminates BEFORE Pj can even check its own while condition. Pi enters the critical section. Pj, at step 3, checks: flag[i] && turn == i = true && true = true Pj must wait. ───────────────────────────────────────────────────────────────────────CONCLUSION:───────────────────────────────────────────────────────────────────────After Pi starts its entry section:- If Pj is not interested → Pi enters immediately (0 CS by Pj)- If Pj is in CS or entering → Pi enters after Pj's current CS (1 CS by Pj)- If Pj tries to re-enter → Pj's turn = i releases Pi first Therefore, Pj can execute its critical section AT MOST ONCE beforePi enters. ∴ Bounded waiting is satisfied with bound = 1. ∎A bound of 1 is the best possible for a two-process solution when one process might already be in the critical section. You cannot guarantee entry before the current CS execution completes. Peterson's Solution achieves this optimal bound.
A powerful technique for reasoning about concurrent systems is identifying invariants—properties that remain true throughout execution. Let's identify the key invariants of Peterson's Solution:
Invariant 1: Flag Represents Intent
1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465
════════════════════════════════════════════════════════════════════════INVARIANTS OF PETERSON'S SOLUTION════════════════════════════════════════════════════════════════════════ INVARIANT I1: Flag Indicates Entry Section or CS─────────────────────────────────────────────────────────────────────── flag[i] = true ⟹ Pi is at L1, L2, L3, or L4 flag[i] = false ⟹ Pi is at L0 or L5 (not interested/just exited) This invariant holds because: - flag[i] is set to true only at L1 (entry section) - flag[i] is set to false only at L5 (exit section) - After L5, process returns to L0 ────────────────────────────────────────────────────────────────────────INVARIANT I2: Turn Was Set By Someone─────────────────────────────────────────────────────────────────────── After initialization, turn ∈ {0, 1} always. At any time t > 0, turn was most recently set by either P0 or P1. This is trivially true because: - turn is only written in L2 of each process - Each process sets turn to j (the other process) - So turn alternates between 0 and 1 ────────────────────────────────────────────────────────────────────────INVARIANT I3: Mutual Exclusion Invariant─────────────────────────────────────────────────────────────────────── ¬(P0 at L4 ∧ P1 at L4) "It is never the case that both processes are in the critical section." This invariant is exactly what we proved in Theorem 1. ────────────────────────────────────────────────────────────────────────INVARIANT I4: Waiting Implies Intent Is Known─────────────────────────────────────────────────────────────────────── If Pi is at L3 (spinning in while loop), then flag[i] = true. This holds because: - L1 (flag[i] = true) is executed before L3 - L5 (flag[i] = false) is executed after L4, which is after L3 ────────────────────────────────────────────────────────────────────────INVARIANT I5: CS Implies Flag Is True─────────────────────────────────────────────────────────────────────── If Pi is at L4 (in CS), then flag[i] = true. This holds because: - Pi sets flag[i] = true at L1 - Pi only clears flag[i] at L5, which is after L4 ────────────────────────────────────────────────────────────────────────DERIVED INVARIANT I6: One Waiter Rule─────────────────────────────────────────────────────────────────────── If flag[0] = flag[1] = true (both interested), then at most one process is executing the while loop body (spinning) while the other is either still before the while check or in CS. This follows because: - If both flags are true, the turn variable distinguishes them - For one process, turn == j (other's id) → waits - For the other, turn != j → proceeds - They cannot both have turn == their_j (that would mean turn == 0 and turn == 1 simultaneously, impossible)Invariants are powerful because they give us 'checkpoints' in our reasoning. If we prove an invariant holds initially and is preserved by every transition, we know it holds forever. Complex proofs often reduce to showing that invariants imply the desired properties.
Beyond the three main properties, we should also verify that Peterson's Solution is free from deadlock and livelock.
Deadlock Freedom:
A deadlock occurs when processes are waiting indefinitely for each other, with no process able to proceed.
Theorem 4: Peterson's Solution is deadlock-free.
1234567891011121314151617181920212223242526272829
════════════════════════════════════════════════════════════════════════THEOREM 4: DEADLOCK FREEDOM════════════════════════════════════════════════════════════════════════ CLAIM: Peterson's Solution never experiences a deadlock where both processes wait forever in their while loops. PROOF BY CONTRADICTION:─────────────────────────────────────────────────────────────────────── Assume deadlock: both P0 and P1 are stuck at L3, spinning forever. For P0 to be stuck at L3: - flag[1] = true AND turn = 1 For P1 to be stuck at L3: - flag[0] = true AND turn = 0 But this requires: - turn = 1 (for P0 to wait) - turn = 0 (for P1 to wait) These cannot both be true simultaneously!turn is a single integer; it has exactly one value at any time. Therefore, at most one process can be stuck in its while loop.The other process's condition must be false, so it can proceed. ∴ Deadlock is impossible. ∎Livelock Freedom:
A livelock occurs when processes are actively executing but making no progress—like two people repeatedly stepping aside for each other in a hallway.
Theorem 5: Peterson's Solution is livelock-free.
123456789101112131415161718192021222324252627282930313233343536373839404142434445
════════════════════════════════════════════════════════════════════════THEOREM 5: LIVELOCK FREEDOM════════════════════════════════════════════════════════════════════════ CLAIM: Peterson's Solution does not exhibit livelock behavior. PROOF:─────────────────────────────────────────────────────────────────────── A livelock would require processes to repeatedly change state withoutentering the critical section. In Peterson's Solution, consider a process Pi at L3 (while loop). Pi can only exit the loop (make progress) when: - flag[j] becomes false, OR - turn becomes i (turn != j) Once either condition becomes true, Pi immediately exits the loopand enters CS. There is no mechanism for the condition to oscillate. Specifically: - flag[j] changes only when Pj modifies it at L1 or L5 - turn changes only when a process executes L2 If Pj is also at L3 (spinning): - Pj cannot execute L2 (already past L2) - Pj cannot execute L1 or L5 (stuck in L3) - So flag and turn are STABLE while both are at L3 With stable flag and turn, one process's condition is false (by the deadlock freedom proof), so that process enters CS. If Pj is not at L3: - Pj is at L0, L1, L2, L4, or L5 - If at L0: flag[j] = false, Pi enters - If at L1, L2: flag[j] may become true, but Pj will set turn = i, releasing Pi - If at L4: Pj will eventually exit, setting flag[j] = false, releasing Pi - If at L5: flag[j] = false, Pi enters In all cases, the waiting process makes progress in finite time. ∴ Livelock is impossible. ∎A key insight is that when both processes are spinning at L3, neither can change flag or turn values. This stability ensures that the turn value stays fixed, making exactly one condition false. Unlike some flawed algorithms, there's no 'after you—no, after you' loop in Peterson's Solution.
Our hand proofs above provide confidence in Peterson's Solution's correctness, but there are more rigorous approaches used in industry and research:
1. Model Checking:
Model checkers exhaustively explore all possible interleavings of a concurrent program. Tools like SPIN, TLA+, or Alloy can verify Peterson's Solution automatically.
1234567891011121314151617181920212223242526272829303132333435
// PROMELA model for Peterson's Solution (SPIN model checker)// This model can be automatically verified for correctness bool flag[2];byte turn;byte critical = 0; // Counter for processes in CS (should never exceed 1) active [2] proctype process() { byte i = _pid; // Process ID (0 or 1) byte j = 1 - i; // Other process do :: // Main loop - repeat forever // Non-critical section skip; // Entry section flag[i] = true; turn = j; // Busy wait (flag[j] == false || turn != j); // Critical section critical++; assert(critical == 1); // Mutual exclusion property critical--; // Exit section flag[i] = false; od} // To verify: spin -a peterson.pml && gcc -o pan pan.c && ./pan// SPIN will explore all interleavings and check the assertion2. TLA+ Specification:
TLA+ (Temporal Logic of Actions) provides a formal mathematical framework for specifying and verifying concurrent systems.
12345678910111213141516171819202122232425262728293031323334353637383940414243444546
----------------------------- MODULE Peterson -----------------------------EXTENDS Integers VARIABLES flag, turn, pc Procs == {0, 1}vars == <<flag, turn, pc>> Init == /\ flag = [i \in Procs |-> FALSE] /\ turn \in Procs \* Initial value doesn't matter /\ pc = [i \in Procs |-> "non-critical"] \* Process i wants to enter critical section WantEnter(i) == /\ pc[i] = "non-critical" /\ flag' = [flag EXCEPT ![i] = TRUE] /\ pc' = [pc EXCEPT ![i] = "set_turn"] /\ turn' = turn SetTurn(i) == /\ pc[i] = "set_turn" /\ turn' = 1 - i \* Set turn to other process /\ pc' = [pc EXCEPT ![i] = "wait"] /\ flag' = flag Wait(i) == /\ pc[i] = "wait" /\ (flag[1-i] = FALSE \/ turn = i) /\ pc' = [pc EXCEPT ![i] = "critical"] /\ UNCHANGED <<flag, turn>> Critical(i) == /\ pc[i] = "critical" /\ pc' = [pc EXCEPT ![i] = "exit"] /\ UNCHANGED <<flag, turn>> Exit(i) == /\ pc[i] = "exit" /\ flag' = [flag EXCEPT ![i] = FALSE] /\ pc' = [pc EXCEPT ![i] = "non-critical"] /\ turn' = turn \* Mutual exclusion propertyMutualExclusion == ~(pc[0] = "critical" /\ pc[1] = "critical") \* The specificationNext == \E i \in Procs: WantEnter(i) \/ SetTurn(i) \/ Wait(i) \/ Critical(i) \/ Exit(i) Spec == Init /\ [][Next]_vars /\ WF_vars(Next)=============================================================================While hand proofs build intuition, formal verification tools catch subtle bugs that humans miss. Amazon uses TLA+ for their distributed systems. Tools like SPIN have been used to verify the Linux kernel's synchronization. Learning these tools is valuable for critical systems work.
We have rigorously proven that Peterson's Solution satisfies all the requirements for a correct critical section solution. Let's summarize our findings:
Proof Techniques Applied:
What's Next:
Despite its proven correctness under the assumed model, Peterson's Solution has significant limitations in practice. In the final page, we'll explore why this elegant algorithm doesn't work correctly on modern hardware and what alternatives exist.
You have now seen formal proofs of Peterson's Solution's correctness. These proof techniques—contradiction, case analysis, invariants—are fundamental to reasoning about concurrent systems. While the proofs are for a simple two-process algorithm, the methods scale to analyzing more complex synchronization protocols.