Loading learning content...
In 1981, sixteen years after Dekker's Algorithm was published, Gary L. Peterson introduced a simpler solution to the two-process mutual exclusion problem. Peterson's Algorithm achieves the same three guarantees—mutual exclusion, progress, and bounded waiting—with significantly less complexity.
This development raises important questions:
This page provides a comprehensive comparison of the two algorithms, examining their structures side-by-side, analyzing their differences, and understanding the trade-offs between them. By understanding both, you'll gain deeper insight into synchronization algorithm design.
By the end of this page, you will understand the structural differences between Dekker's and Peterson's algorithms, why Peterson's version is simpler without sacrificing correctness, the historical significance of the simplification, and the design principles that make simpler synchronization algorithms easier to verify.
Let's examine both algorithms side-by-side to see their structural similarities and differences.
Dekker's Algorithm:
12345678910111213141516171819202122232425262728293031323334353637
// DEKKER'S ALGORITHM (1965)// First correct software solution for 2-process mutual exclusion // Shared variablesboolean flag[2] = {FALSE, FALSE}; // Intent flagsint turn = 0; // Priority indicator void dekker_process(int i) { int j = 1 - i; // Other process while (TRUE) { // ===== ENTRY PROTOCOL ===== flag[i] = TRUE; // Express intent while (flag[j] == TRUE) { // Contention check if (turn != i) { // Not my turn? flag[i] = FALSE; // Back off while (turn != i) { // Wait for turn // Busy wait } flag[i] = TRUE; // Re-assert intent } // Else: my turn, stay in loop waiting for flag[j] = FALSE } // ===== CRITICAL SECTION ===== critical_section(); // ===== EXIT PROTOCOL ===== turn = j; // Give turn to other flag[i] = FALSE; // Clear intent // ===== REMAINDER SECTION ===== remainder_section(); }}Peterson's Algorithm:
123456789101112131415161718192021222324252627282930
// PETERSON'S ALGORITHM (1981)// Simplified solution for 2-process mutual exclusion // Shared variablesboolean flag[2] = {FALSE, FALSE}; // Intent flagsint turn; // Priority indicator void peterson_process(int i) { int j = 1 - i; // Other process while (TRUE) { // ===== ENTRY PROTOCOL ===== flag[i] = TRUE; // Express intent turn = j; // Give turn to other (be polite!) while (flag[j] == TRUE && turn == j) { // Busy wait } // ===== CRITICAL SECTION ===== critical_section(); // ===== EXIT PROTOCOL ===== flag[i] = FALSE; // Clear intent // ===== REMAINDER SECTION ===== remainder_section(); }}Peterson's entry protocol is 4 lines; Dekker's is 10+ lines. Peterson has no nested loops; Dekker has a loop inside a loop with conditionals. Peterson's exit protocol is 1 line; Dekker's is 2. This dramatic simplification is the core contribution of Peterson's work.
The two algorithms share the same shared variables (two flags and a turn indicator) but use them quite differently:
Difference 1: Turn Assignment Timing
In Dekker's Algorithm:
turn = j gives priority to the other process for the next contentionIn Peterson's Algorithm:
turn = j says "I'm willing to let you go first if we both want in"This is the key innovation. Peterson moved the turn assignment to the entry section and inverted its meaning.
| Aspect | Dekker's Algorithm | Peterson's Algorithm |
|---|---|---|
| When set | Exit protocol (after CS) | Entry protocol (before waiting) |
| Who sets to j | Process i after exiting | Process i upon entering protocol |
| Semantic meaning | "It's your turn next" | "After you, if we both want in" |
| Initial value | Matters (0 or 1) | Doesn't matter (gets set before use) |
| Used how | Determines who backs off | Part of compound wait condition |
Difference 2: The Wait Condition
In Dekker's Algorithm:
while (flag[j] == TRUE) {
// Complex inner logic with backoff
}
The wait condition only checks the flag. The turn logic is handled inside the loop.
In Peterson's Algorithm:
while (flag[j] == TRUE && turn == j) {
// Simple busy wait
}
The wait condition checks both flag and turn. There's no inner logic—just spin.
This compound condition is Peterson's second key innovation. By checking both conditions together, there's no need for the complex back-off protocol.
1234567891011121314151617181920212223242526
// PETERSON'S WAIT CONDITION ANALYSIS// while (flag[j] && turn == j) // Exit condition: flag[j] == FALSE OR turn != j // Case 1: flag[j] == FALSE// Meaning: Other process doesn't want critical section// Action: Enter immediately (no contention)// Same as Dekker // Case 2: flag[j] == TRUE but turn != j (i.e., turn == i)// Meaning: Other wants CS, but WE have priority// Action: Enter immediately (we win contention)// Difference: In Dekker, we'd spin on flag[j] with priority // Case 3: flag[j] == TRUE and turn == j// Meaning: Other wants CS and THEY have priority// Action: Wait (they should enter)// Difference: No backoff needed! Just keep checking. // WHY NO BACKOFF NEEDED:// - We set turn = j BEFORE waiting// - If other also enters entry protocol, they set turn = i// - The LAST write to turn wins// - Exactly one of us has turn, the other enters// - No possibility of both waiting forever!Peterson's insight was that if both processes set turn before waiting, the last writer determines the value. One process will see turn = i, the other will see turn = j. They cannot both see turn values that keep them waiting. This eliminates the deadlock that forces Dekker's backoff protocol.
Peterson's Algorithm is objectively simpler by several metrics:
1. Lines of Code
| Component | Dekker | Peterson |
|---|---|---|
| Entry protocol | ~12 | 4 |
| Exit protocol | 2 | 1 |
| Nested loops | Yes | No |
| Conditionals in loop | 2 | 0 (compound condition) |
2. Cognitive Complexity
Dekker's Algorithm requires understanding:
Peterson's Algorithm requires understanding:
3. Formal Verification Difficulty
Proving Dekker correct requires reasoning about:
Proving Peterson correct is more direct:
The Elegance of Peterson's Insight:
By setting turn = j in the entry protocol, Peterson creates a situation where:
This is conceptually simpler: each process says "after you" and the last one to say it actually yields. In Dekker, the yielding is more complex: check priority, back off, wait for turn, retry.
Peterson's Algorithm is the standard choice for teaching mutual exclusion because students can understand it in 15 minutes. Dekker's typically requires an hour. For pedagogical purposes, Peterson achieves the same learning objectives (proving mutual exclusion, understanding atomic operations, etc.) with less confusion.
Both algorithms satisfy the three requirements for mutual exclusion solutions. Let's compare how they achieve each:
Mutual Exclusion:
Progress:
1234567891011121314151617181920212223242526
PROGRESS PROPERTY Goal: If no process is in CS and some want to enter, one will enter. DEKKER: - If only one process wants in: flag[j] = FALSE, enter immediately - If both want in: turn determines priority - Priority process spins on flag[j] - Non-priority process backs off, clears flag[j] - Priority process sees flag[j] = FALSE, enters - Progress achieved via backoff creating asymmetry PETERSON: - If only one process wants in: - flag[j] = FALSE makes condition FALSE, enter immediately - If both want in: - Both set turn to each other - Last write wins (say turn = i survives) - Process i sees turn != j(= i), condition FALSE, enters - Process j sees turn == j AND flag[i] = TRUE, waits - Progress achieved via turn breaking tie Key insight: Peterson's progress relies on last-write semanticsDekker's progress relies on explicit backoff Both work, but Peterson's is more direct.Bounded Waiting:
12345678910111213141516171819202122232425262728
BOUNDED WAITING PROPERTY Goal: After a process wants to enter, at most N-1 entries by other processes before this process enters. (For 2 processes: at most 1 entry by the other) DEKKER: - Process i wants to enter, backs off (turn = j) - Process j enters, exits, sets turn = i - Process i now has turn, will win next contention - Bound: 1 entry by other process PETERSON: - Process i wants to enter, sets turn = j - If j in CS, i waits (turn = j, flag[j] = TRUE) - When j exits, j sets flag[j] = FALSE - i sees flag[j] = FALSE, enters - Even if j immediately reenters: - j sets flag[j] = TRUE, turn = i - i sees turn == i (not j), enters - Bound: 0 or 1 entry by other process Key insight: Peterson achieves bounded waiting throughthe turn assignment in entry protocol. The waiter isalways favored after the first exit. Both algorithms provide the same bound (1),achieved through different mechanisms.| Property | Dekker Mechanism | Peterson Mechanism | Same Guarantee? |
|---|---|---|---|
| Mutual Exclusion | Backoff + flag clear | Compound wait condition | Yes |
| Progress | Backoff creates asymmetry | Last-write turn semantics | Yes |
| Bounded Waiting | Turn transfer on exit | Turn assignment on entry | Yes (bound = 1) |
A natural question arises: if Peterson's solution is simpler, why did it take 16 years to discover (1965 → 1981)?
The State of the Art in 1965:
When Dekker designed his algorithm:
No established theory: There was no framework for reasoning about concurrent programs. Dekker had to invent his reasoning approach alongside the algorithm.
First solution pressure: Dekker wasn't trying to find the simplest solution—he was trying to find any correct solution. Once a working algorithm exists, simplification is a different (often easier) problem.
Exit-focused thinking: Dekker's approach of transferring turn on exit is intuitive: "I'm done, you can go now." The entry-focused approach of Peterson is less intuitive: "I'll let you go first... and actually mean it only if you also want in."
No alternative examples: Without seeing multiple solutions, it's hard to identify unnecessary complexity. Only after Dekker's algorithm was studied for years did its complexity become apparent.
Between 1965 and 1981, the field wasn't idle. Dijkstra developed semaphores (1965), Hansen and Hoare developed monitors (1970s), Lamport developed the Bakery Algorithm (1974), and formal verification techniques matured. Peterson's simplification benefited from 16 years of accumulated understanding about what mattered in synchronization.
Why Peterson's Insight Was Non-Obvious:
Peterson's key insight—setting turn in the entry protocol to yield to the other—seems backwards at first:
"Wait, I want to enter, so why am I giving turn to the other process?"
The answer is subtle: by both processes offering priority to each other, the last offer stands. If I offer after you offer, you have priority. If you offer after I offer, I have priority. This converts a potential deadlock (both claiming priority) into orderly sequencing (exactly one has priority).
This is an example of a counterintuitive correct solution: doing the opposite of what seems right (yielding when you want to acquire) achieves the desired result (actually acquiring).
Peterson's Contribution:
Peterson's 1981 paper, "Myths About the Mutual Exclusion Problem," not only presented the simpler algorithm but also debunked common misconceptions:
Myth: Mutual exclusion requires complex back-off protocols
Reality: A simple compound wait condition suffices
Myth: Turn must be managed carefully to avoid starvation
Reality: Setting turn on entry (yielding) automatically provides fairness
Myth: Simpler algorithms sacrifice correctness properties
Reality: Peterson's algorithm achieves all three properties with less code
In practice, neither algorithm is used in production systems—hardware primitives (compare-and-swap, test-and-set) provide better performance. However, for academic and specific constrained contexts, different situations might favor one over the other:
Prefer Peterson When:
Prefer Dekker When:
| Consideration | Dekker | Peterson |
|---|---|---|
| Code complexity | Higher | Lower |
| Proof complexity | Higher | Lower |
| Teaching value | Historical context | Standard example |
| Memory operations | More (backoff writes) | Fewer |
| Scalability to n processes | Neither scales directly | Neither scales directly |
| Modern hardware | Neither is used | Neither is used |
For actual systems programming, use hardware-supported primitives (mutexes, spinlocks with CAS, etc.). Both Dekker's and Peterson's algorithms require sequential consistency which modern processors don't guarantee without explicit memory barriers. They are teaching tools, not production solutions.
The Real Takeaway:
The comparison between Dekker and Peterson isn't about choosing one for implementation—it's about:
We've comprehensively compared Dekker's and Peterson's algorithms. Let's consolidate the key insights:
What's Next:
Our final page examines the limitations of Dekker's Algorithm (which also apply to Peterson's). We'll explore why these algorithms fail on modern hardware without memory barriers, why they don't scale to more than two processes without modification, and how these limitations led to the development of hardware synchronization primitives.
You now understand the key differences between Dekker's and Peterson's algorithms, why Peterson's version is simpler without sacrificing correctness, and the historical context of this evolution. This comparison illuminates fundamental principles of synchronization algorithm design. Next, we'll explore the limitations that make these algorithms unsuitable for modern systems.