Peterson, Dekker and software solutions
Mutual exclusion for two processes with nothing but ordinary reads and writes: strict alternation, the flag attempts and why each fails, Peterson's algorithm and why it works, Dekker's and the bakery algorithm, the checks of mutual exclusion, progress and bounded waiting, and the memory-ordering caveat.
Why it exists
Before the hardware and the operating system provide locks, can two processes protect a critical section using only ordinary variables? Yes, but it is surprisingly easy to get wrong, and each plausible attempt fails in a different way: some let both processes in, some make both wait for ever, some make a process wait for one that is not even competing. Studying the failures teaches the three requirements of the critical-section problem, and Peterson's algorithm is the standard correct solution for two processes. GATE asks you to decide, for a given entry/exit code, which of mutual exclusion, progress and bounded waiting hold, and to explain why Peterson's algorithm works.
Prerequisites
You need the three requirements and the entry, critical, exit, remainder structure from critical section and race conditions.
Precise definitions
Two processes, numbered 1 and 2, each running repeat { remainder; entry; critical section; exit } forever. "Me" is process , "you" is the other, . All loops of the form while (c) ; are busy waits: the process spins until the condition becomes false.
Attempt 1: strict alternation. One shared variable turn.
entry: while (turn != i) ;
exit: turn = j;
Mutual exclusion holds. Progress fails: the processes must enter alternately. If process 2 is in a long remainder section and turn = 2, process 1 cannot enter even though the critical section is free and nobody is competing.
Attempt 2: raise your flag, then wait. A shared array flag[2], true meaning "I want to enter".
entry: flag[i] = true; while (flag[j]) ;
exit: flag[i] = false;
Mutual exclusion holds, but if both raise their flags before either tests, each waits for the other for ever: deadlock (progress fails).
Attempt 3: test the other's flag, then raise your own.
entry: while (flag[j]) ; flag[i] = true;
exit: flag[i] = false;
Mutual exclusion fails: both can find the other's flag false and then both raise theirs and enter. The test and the setting are separate steps, so one process can slip in between the other's two.
Peterson's algorithm. Two shared variables: flag[2] and turn.
entry: flag[i] = true;
turn = j;
while (flag[j] && turn == j) ;
exit: flag[i] = false;
A process announces its wish (flag[i] = true), then politely gives the other the turn (turn = j), and waits only while the other also wants to enter and it is the other's turn. Whoever writes turn last is the one that waits, so exactly one of two simultaneous requesters waits.
Why it works.
- Mutual exclusion. Suppose both are in the critical section. Both flags have been true since each process raised its own, and stay true until it leaves. Each process wrote
turnonce on its way in, and the two writes happened one after the other: say process wrote last, so it wroteturn = j, and nobody writesturnagain while both are on their way in or inside. Then process 's testflag[j] && turn == jis true, so process cannot have passed it: a contradiction. - Progress. A process waits only while the other has its flag raised and has the turn; a process that is in its remainder section has its flag down, so it cannot hold up the other. No deadlock:
turnhas one value, so one of the two loop conditions is false. - Bounded waiting. After process has raised its flag and set
turn = j, process can enter at most once more before does: when leaves, and tries again, it setsturn = iand must wait.
The order of the first two instructions matters. If turn = j is executed before flag[i] = true, mutual exclusion fails: the page's animation gives the schedule.
Dekker's algorithm (the first correct two-process solution, historically) uses flag[2] and turn too, but a process whose flag is raised, finding the other also wants to enter and it is not its turn, lowers its flag and waits for turn before trying again. It is more complicated than Peterson's.
Lamport's bakery algorithm solves the problem for processes. A process takes a ticket number one larger than the largest it sees (like a bakery counter), then waits until every other process holding a smaller number (ties broken by process id) has finished. Shared arrays choosing[n] and number[n] make taking a number safe. It satisfies all three requirements, uses only reads and writes, and gives first come first served.
Properties of busy waiting. The waiting process wastes CPU cycles (a spinlock). Busy-wait solutions are suitable only for very short critical sections or multiprocessor kernels, and they can cause priority inversion (a high-priority spinning process may keep the CPU from a low-priority one that holds the critical section).
Memory ordering caveat. These algorithms assume that reads and writes by one process become visible to the other in program order (sequential consistency). Modern processors buffer and reorder stores, so Peterson's algorithm needs memory barriers or atomic operations to work on real hardware.
How it works
The animations are real schedules found by exploring every interleaving. They follow each algorithm in turn. Strict alternation first: the schedule ends with thread 2 stuck although thread 1 is in its remainder section.
Strict alternation: a thread waits for a process that is not competing: start
Interleaving found by search, not typed▶remainder sectionwhile (turn != 1) ;critical sectionturn = 2
▶remainder sectionwhile (turn != 2) ;critical sectionturn = 1
Step 0
turn is 1. Each thread waits for its turn, enters the critical section, then hands the turn to the other. Mutual exclusion holds in every interleaving, but the threads are forced to take turns even if one of them does not want to enter.
Raise the flag, then wait: the shortest schedule that ends in deadlock.
Raise your flag, then wait: both threads wait for ever: start
Interleaving found by search, not typed▶remainder sectionflag1 = truewhile (flag2) ;critical sectionflag1 = false
▶remainder sectionflag2 = truewhile (flag1) ;critical sectionflag2 = false
Step 0
Each thread first sets its own flag ("I want to enter") and then waits while the other's flag is set. This is the shortest schedule that ends in deadlock.
Test, then set: the shortest schedule that puts both threads in the critical section.
Test the other's flag, then set your own: both get in: start
Interleaving found by search, not typed▶remainder sectionwhile (flag2) ;flag1 = truecritical sectionflag1 = false
▶remainder sectionwhile (flag1) ;flag2 = truecritical sectionflag2 = false
Step 0
Each thread waits until the other's flag is false and only then sets its own. The check and the set are two separate instructions, so another thread can slip in between them. This is the shortest schedule that puts both threads in the critical section.
Peterson's algorithm with both threads competing. T2 writes turn last and so it is T2 that waits; after T1 leaves, T2 enters.
Peterson's algorithm with both threads competing: start
Interleaving found by search, not typed▶remainder sectionflag1 = trueturn = 2while (flag2 && turn == 2) ;critical sectionflag1 = false
▶remainder sectionflag2 = trueturn = 1while (flag1 && turn == 1) ;critical sectionflag2 = false
Step 0
flag1 = flag2 = false and turn = 1. Both threads want to enter. Each sets its flag and then writes turn = the OTHER thread's number; whoever writes turn last gives way, because the loop makes a thread wait only while the other wants in AND it is the other's turn.
And the same algorithm with its first two entry instructions swapped, which breaks mutual exclusion.
Peterson with the first two instructions swapped: mutual exclusion fails: start
Interleaving found by search, not typed▶remainder sectionturn = 2flag1 = truewhile (flag2 && turn == 2) ;critical sectionflag1 = false
▶remainder sectionturn = 1flag2 = truewhile (flag1 && turn == 1) ;critical sectionflag2 = false
Step 0
The same algorithm but turn is assigned BEFORE the flag is raised. The shortest schedule that puts both threads in the critical section is shown.
Worked examples
EX 1Check all five algorithms by exploring every state
EasyA program explores every reachable state of each algorithm (two threads, each in a loop) and reports whether mutual exclusion is violated, whether the system can deadlock, and whether a thread can be stuck at its wait while the other is in its remainder section.
#include <stdio.h>
#include <string.h>
/* Two threads, 0 and 1. A state is (pc0, pc1, flag0, flag1, turn). The program explores EVERY reachable state of each algorithm. */
enum alg { ALTERNATE, FLAG_FIRST, CHECK_THEN_SET, PETERSON, PETERSON_SWAPPED };
struct st { int pc[2], flag[2], turn; };
/* instruction numbers: 0 remainder section, then the entry code, the critical section, the exit code (which returns to 0) */
static int cs_pc(enum alg a) { return a == ALTERNATE ? 2 : a == FLAG_FIRST || a == CHECK_THEN_SET ? 3 : 4; }
static int wait_pc(enum alg a) { return a == ALTERNATE ? 1 : a == FLAG_FIRST ? 2 : a == CHECK_THEN_SET ? 1 : 3; }
/* returns 1 and fills *out if thread i can take a step, 0 if it is blocked at a loop */
static int move(enum alg a, const struct st *s, int i, struct st *out) {
int j = 1 - i, pc = s->pc[i];
*out = *s;
switch (a) {
case ALTERNATE: /* 0 rem, 1 while (turn != i), 2 cs, 3 turn = j */
if (pc == 1 && s->turn != i) return 0;
if (pc == 3) { out->turn = j; out->pc[i] = 0; return 1; }
out->pc[i] = pc + 1; return 1;
case FLAG_FIRST: /* 0 rem, 1 flag[i] = 1, 2 while (flag[j]), 3 cs, 4 flag[i] = 0 */
if (pc == 1) out->flag[i] = 1;
if (pc == 2 && s->flag[j]) return 0;
if (pc == 4) { out->flag[i] = 0; out->pc[i] = 0; return 1; }
out->pc[i] = pc + 1; return 1;
case CHECK_THEN_SET: /* 0 rem, 1 while (flag[j]), 2 flag[i] = 1, 3 cs, 4 flag[i] = 0 */
if (pc == 1 && s->flag[j]) return 0;
if (pc == 2) out->flag[i] = 1;
if (pc == 4) { out->flag[i] = 0; out->pc[i] = 0; return 1; }
out->pc[i] = pc + 1; return 1;
default: /* Peterson: 0 rem, 1 and 2 the two entry writes, 3 while (flag[j] && turn == j), 4 cs, 5 flag[i] = 0 */
if (pc == 1 || pc == 2) {
int first_is_flag = a == PETERSON ? pc == 1 : pc == 2;
if (first_is_flag) out->flag[i] = 1; else out->turn = j;
}
if (pc == 3 && s->flag[j] && s->turn == j) return 0;
if (pc == 5) { out->flag[i] = 0; out->pc[i] = 0; return 1; }
out->pc[i] = pc + 1; return 1;
}
}
static int code(const struct st *s) { return (((s->pc[0] * 6 + s->pc[1]) * 2 + s->flag[0]) * 2 + s->flag[1]) * 2 + s->turn; }
static void explore(enum alg a, const char *name) {
static unsigned char seen[6 * 6 * 8];
static struct st queue[6 * 6 * 8];
memset(seen, 0, sizeof seen);
int head = 0, tail = 0, states = 0, me = 0, dead = 0, prog = 0;
struct st start = {{0, 0}, {0, 0}, 0};
queue[tail++] = start; seen[code(&start)] = 1;
while (head < tail) {
struct st s = queue[head++], n;
states++;
if (s.pc[0] == cs_pc(a) && s.pc[1] == cs_pc(a)) me = 1;
int moved = 0;
for (int i = 0; i < 2; i++) {
if (move(a, &s, i, &n)) {
moved = 1;
if (!seen[code(&n)]) { seen[code(&n)] = 1; queue[tail++] = n; }
} else if (s.pc[1 - i] == 0 && s.pc[i] == wait_pc(a)) prog = 1; /* stuck at its wait while the other is outside the competition */
}
if (!moved) dead = 1;
}
printf("%-22s states %2d mutual exclusion violated: %d deadlock: %d waits for a thread in its remainder section: %d\n", name, states, me, dead, prog);
}
int main(void) {
explore(ALTERNATE, "strict alternation");
explore(FLAG_FIRST, "flag first");
explore(CHECK_THEN_SET, "check then set");
explore(PETERSON, "Peterson");
explore(PETERSON_SWAPPED, "Peterson, swapped");
return 0;
}strict alternation states 16 mutual exclusion violated: 0 deadlock: 0 waits for a thread in its remainder section: 1
flag first states 21 mutual exclusion violated: 0 deadlock: 1 waits for a thread in its remainder section: 0
check then set states 25 mutual exclusion violated: 1 deadlock: 0 waits for a thread in its remainder section: 0
Peterson states 42 mutual exclusion violated: 0 deadlock: 0 waits for a thread in its remainder section: 0
Peterson, swapped states 72 mutual exclusion violated: 1 deadlock: 0 waits for a thread in its remainder section: 0Reading it. Each line is a proof by exhaustion for that algorithm, over every possible schedule. The table of results:
| Algorithm | Mutual exclusion | Progress | Bounded waiting |
|---|---|---|---|
| Strict alternation | holds | fails (blocked by a process in its remainder section) | holds (the other enters at most once) |
| Flag first | holds | fails (deadlock) | not meaningful: both wait for ever |
| Check then set | fails | holds | not meaningful |
| Peterson | holds | holds | holds (the other enters at most once after the request) |
| Peterson, swapped | fails | holds | not meaningful |
EX 2Peterson in a contended run
MediumBoth threads want to enter: T1 sets flag1, then turn = 2; T2 sets flag2, then turn = 1. turn now holds 1 because T2 wrote last. T1's loop condition flag2 && turn == 2 is false (turn is 1), so T1 enters; T2's condition flag1 && turn == 1 is true, so T2 spins. When T1 clears flag1, T2's condition becomes false and T2 enters. So at no point are both inside, and nobody waits for ever. Had T1 been the last to write turn, T1 would have waited. The animation follows this schedule instruction by instruction.
EX 3Why the order of the first two instructions matters
MediumIn Peterson's algorithm the flag must be raised before turn is written. In the swapped version, T1 writes turn = 2 and is then preempted; T2 writes turn = 1, raises flag2, finds flag1 still false and enters; T1 then raises flag1; its loop flag2 && turn == 2 is false (turn is 1), so T1 enters too. Both are inside. The search finds exactly this kind of schedule and it is the one in the last animation. The lesson: "I want to enter" must be visible to the other process before the process hands over the turn.
EX 4Which requirement does this solution violate?
GATE levelA solution: entry: while (turn != i) ; exit: turn = j; (strict alternation). Mutual exclusion: holds. Progress: violated, because process 1 may be unable to enter while the critical section is empty and process 2 is in its remainder section with the turn. Bounded waiting: after process 1 requests, process 2 can enter at most once ahead of it (if it had the turn), so it holds. A solution entry: flag[i] = true; while (flag[j]) ; has mutual exclusion, but both flags can be true together, so both loop for ever: progress fails by deadlock. In a multiple-choice question, check each of the three requirements in this order: can both be inside? Can both (or one with the other outside) be stuck? Can one be overtaken without bound?
EX 5The bakery algorithm in a nutshell
GATE levelThree processes P0, P1, P2 want to enter. They take tickets: P0 gets 1, P1 gets 2, P2 gets 2 (P1 and P2 chose at the same time and both saw the maximum 1). Order of entry: the smaller ticket first, ties broken by the smaller process id: P0 (1), then P1 (2, id 1), then P2 (2, id 2). It is first come first served, and it works for any number of processes, unlike Peterson's algorithm. The cost is that the ticket numbers can grow without bound and the entry code reads every other process's variables.
Cheat box
Cheat box
- Strict alternation: ME yes, progress no. Flag then wait: ME yes, deadlock. Test then set: ME no.
- Peterson:
flag[i] = true; turn = j; while (flag[j] && turn == j) ;exitflag[i] = false. ME, progress, bounded waiting (the other enters at most once) all hold. - The LAST writer of
turnwaits. Flag before turn, never the reverse. - Dekker: first correct two-process solution. Bakery: processes, tickets, ties by id.
- Busy waiting wastes CPU (spinlock); software solutions assume reads and writes in program order (need barriers on real hardware).
- Check a proposed solution in this order: ME, progress, bounded waiting.
Traps and GATE patterns
Connections
Builds on
Across subjects
These solutions are the base on which hardware instructions such as test-and-set and the semaphore are justified. A circular wait is the cause of the deadlock in "flag first" (deadlock). The need for memory barriers comes from stores being buffered and reordered between processor and memory, as in the write buffers of the cache.
PYQs
5 previous year questions from your book. I re-solved each one myself (1 by running or simulating, 4 by reasoning only); the book's key is shown next to my answer. All PYQs
Consider the following proposed solution for the
critical section problem. There are n processes:
P0...Pn-1. In the code, function pmax returns an integer
not smaller than any of its arguments. For all i, t[i] is
initialized to zero.
Code for Pi:
do {
c[i]=1; t[i] = pmax (t[0],...,t[n-1]) +1; c[i]=0;
for every j ≠ i in {0,...,n-1} {
while (c[j]);
while (t[j]! = 0 && t[j]<=t[i]);
}
Critical Section;
t[i]=0;
Remainder Section;
} while (true);
Which one of the following is TRUE about the above
solution?- (a)At most one process can be in the critical section at any time.
- (b)The bounded wait condition is satisfied
- (c)The progress condition is satisfied
- (d)It cannot cause a deadlock.
Consider the following two-process synchronization solution. Process 0 Process 1 Entry: loop while (turn == 1); Entry: loop while (turn == 0); (critical section ) ( Critical section) Exit: turn = 1; Exit: turn = 0; The shared variable turn is initialized to zero. Which one of the following is TRUE?
- (a)This is a correct two-process synchronization solution.
- (b)This solution violates mutual exclusion requirement.
- (c)This solution violates the progress requirement.
- (d)This solution violates the bounded wait requirement. OPERATING SYSTEMS
Two processes X and Y need to access a critical section. Consider the following synchronization construct used by both the processes. Here, varP and varQ are shared variables and both are initialized to false. Which one of the following statements is true?
- (a)The proposed solution prevents deadlock but fails to guarantee mutual exclusion.
- (b)The proposed solution guarantees mutual exclusion but fails to prevent deadlock.
- (c)The proposed solution guarantees mutual exclusion and prevents deadlock.
- (d)The proposed solution fails to prevent deadlock and fails to guarantee mutual exclusion.
Consider the methods used by processes P1 and P2 for accessing their critical sections whenever needed, as given below. The initial values of shared Boolean variables S1 and S2 are randomly assigned. Method Used by P1 Method Used by P2 while (S1 == S2); while (S1! = S2); Critical Section Critical Section S1 = S2; S2 = not (S1); Which one of the following statements describes the properties achieved?
- (a)Mutual exclusion but not progress
- (b)Progress but not mutual exclusion
- (c)Neither mutual exclusion nor progress
- (d)Both mutual exclusion and progress
Two processes, P1 and P2, need to access a critical
section
of
code.
Consider
the
following
synchronization construct used by the processes:
/* P1 */
while (true) {
wants1 = true;
while (wants2 == true);
/* Critical Section */
wants1 = false;
}
/* Remainder section */
/* P2 */
while (true) {
wants2 = true;
while (wants1 == true);
/* Critical Section */
wants2 = false;
}
/* Remainder section */
Here, wants1 and wants2 are shared variables,
initialized to false.
Which one of the following statements is TRUE
about the above construct?- (a)It does not ensure mutual exclusion.
- (b)It does not ensure bounded waiting.
- (c)It requires that processes enter the critical section in strict alternation.
- (d)It does not prevent deadlocks, but ensures mutual exclusion.
Self-check
Question 1
In Peterson's algorithm both processes execute their entry code at the same time and process 1 writes turn last. Which process enters first?
Show answerHide
Process 2. Process 1 wrote turn = 2 last, so its own condition flag[2] && turn == 2 is true and it waits; process 2's condition flag[1] && turn == 1 is false (turn is 2), so process 2 enters.
Question 2
Which requirement does entry: flag[i] = true; while (flag[j]) ; violate and how?
Show answerHide
Progress: if both set their flags, both wait for ever (deadlock).
Question 3
Why does the swapped Peterson (turn before flag) fail?
Show answerHide
A process can write turn, be preempted, and the other process, seeing the first process's flag still down, enters; when the first resumes it raises its flag, finds turn not equal to the other's number and enters too.
Question 4
Does strict alternation allow a process to enter twice in a row?
Show answerHide
No. After a process leaves it hands the turn to the other, so it must wait for the other to take (and release) the turn first.
Revision card
One-screen revision
- Alternation: ME but no progress. Flag first: deadlock. Test-then-set: no ME.
- Peterson:
flag[i] = true; turn = j; while (flag[j] && turn == j) ;— all three requirements; last writer ofturnwaits. - Flag before turn. Bakery for processes. Needs barriers on real hardware.
- Busy waiting wastes CPU.
References
- ·Silberschatz, Galvin, Gagne, Operating System Concepts, Chapter 6 Synchronization Tools (Peterson's solution)
- ·Tanenbaum and Bos, Modern Operating Systems, Chapter 2 Processes and Threads (mutual exclusion with busy waiting: strict alternation, Peterson's solution)
- ·Stallings, Operating Systems: Internals and Design Principles, Chapter 5 Concurrency (software approaches: Dekker's algorithm, Peterson's algorithm)
Verification
Every claim about the five algorithms on this page (strict alternation, flag-first, check-then-set, Peterson, Peterson with its first two entry steps swapped) is established by exhaustively exploring ALL interleavings, in four independent implementations that must agree: the generic model checker in scripts/gen-os-sync-traces.py, hand-written explicit transition systems in scripts/enum-checks/os_sync.py (the state counts 16, 21, 25, 42 and 72 are equal in all of them), the breadth-first search in the C program on this page (npm run verify:c), and tests/os-sync-traces.test.ts on the animations. Bounded waiting is measured as the greatest number of times the other thread can enter the critical section after a thread has made its request (Peterson: 1). The model assumes sequentially consistent memory: on real processors Peterson's algorithm needs memory barriers (or atomic variables) because stores can be reordered or buffered; this cannot be demonstrated by the model and is stated as a known fact. Dekker's and the bakery algorithm are described but not modelled. Textbook chapter titles are from memory. SECOND PASS (2026-11-02, done by the assistant, not a human check): every Self-check question was solved before the printed answer was read and all agreed; the definitions, worked examples, cheat box and traps were read claim by claim and the numbers re-derived by hand. One muddled explanation (why turn = i is wrong) corrected. Status raised to self-checked; verified needs a textbook or human check.
last verified: never
My notes (saved in this browser)