Bakery Algorithm for Process Synchronization
Leslie Lamport came up with this algorithm in 1974, and the inspiration behind it is surprisingly straightforward. Think about how a busy bakery manages its customers. Everyone walks in, grabs a numbered token, and waits for their number to be called. The person with the lowest number gets served first.
Lamport took that same idea and applied it to process synchronization. Unlike Peterson's and Dekker's algorithms which are limited to two processes, the Bakery Algorithm scales to any number of processes without any structural changes.
How It Works
Every process that wants to enter the critical section first picks up a ticket number. The number it picks is one higher than the highest number currently held by any other process. Once it has its number, it waits until all processes with lower numbers have finished. The one holding the smallest number goes in first.
There is one edge case worth mentioning. Two processes can occasionally end up with the same ticket number if they both pick at the same moment. When that happens, the tiebreaker is the process ID. The process with the lower ID gets priority.
The Shared Variables
Two shared arrays keep everything running:
- choosing[i]: A boolean that signals whether Process i is currently in the middle of picking its number. Other processes need to know this so they do not compare numbers while someone is still choosing.
- number[i]: The actual ticket number assigned to Process i. A value of zero means the process is not interested in entering the critical section right now.
Step by Step
When a process wants in, it goes through the following sequence:
Process i wants to enter:
1. choosing[i] = true // "I'm picking a number"
2. number[i] = 1 + max(all numbers) // Take the next ticket
3. choosing[i] = false // "Done picking"
|
v
For each other process j:
|
+-- Wait until choosing[j] == false
| (Don't compare while they're still picking)
|
+-- Wait until:
number[j] == 0 // j is not interested
OR number[j] > number[i] // j has a higher ticket
OR (number[j] == number[i] AND j > i) // Same ticket, but j has higher ID
|
v
Enter Critical Section
|
v
number[i] = 0 // Discard the ticketThe Code
#include <iostream>
#include <thread>
#include <vector>
#include <algorithm>
#include <atomic>
using namespace std;
const int NUM_PROCESSES = 5;
atomic<bool> choosing[NUM_PROCESSES];
atomic<int> number[NUM_PROCESSES];
void bakery_process(int i) {
for (int iter = 0; iter < 5; iter++) {
// Doorway: Pick a ticket number
choosing[i] = true;
number[i] = 1 + *max_element(number, number + NUM_PROCESSES);
choosing[i] = false;
// Wait for all other processes
for (int j = 0; j < NUM_PROCESSES; j++) {
while (choosing[j]); // Wait if they're picking
while (number[j] != 0 && // Wait if they have a ticket AND
(number[j] < number[i] || // their number is smaller, OR
(number[j] == number[i] && j < i))); // same number but lower ID
}
// --- Critical Section ---
cout << "Process " << i << " is in its critical section." << endl;
// --- Exit Section ---
number[i] = 0; // Discard the ticket
cout << "Process " << i << " is in its remainder section." << endl;
}
}
int main() {
vector<thread> processes;
for (int i = 0; i < NUM_PROCESSES; i++) {
choosing[i] = false;
number[i] = 0;
}
for (int i = 0; i < NUM_PROCESSES; i++) {
processes.push_back(thread(bakery_process, i));
}
for (auto &process : processes) {
process.join();
}
return 0;
}Output:
Process 0 is in its critical section.
Process 0 is in its remainder section.
Process 1 is in its critical section.
Process 1 is in its remainder section.
...What It Guarantees
| Requirement | How the Bakery Algorithm Satisfies It |
|---|---|
| Mutual Exclusion | Only the process with the lowest ticket number enters. If two have the same number, the lower process ID wins. At most one process can satisfy both conditions simultaneously. |
| Progress | If the critical section is free, the process with the lowest non-zero ticket number enters immediately. No external process can block it. |
| Bounded Waiting | Ticket numbers are assigned in increasing order. A process that picks a number will eventually hold the lowest number as others finish and discard their tickets. |
What Makes It Worth Using
- No Starvation: Since ticket numbers are assigned in order and every process eventually holds the lowest number, every process is guaranteed a turn. Nobody gets skipped indefinitely.
- Fair by Design: Processes are served in the order they arrived (FCFS ordering), which is as fair as it gets. No priority system causes some processes to consistently jump ahead.
- Scalability: Unlike Peterson's and Dekker's (limited to 2 processes), the Bakery Algorithm handles any number of processes without structural changes.
- Intuitive Logic: The ticket number analogy makes the algorithm easy to understand even before looking at the code.
Bakery vs Peterson's vs Dekker's
| Factor | Peterson's | Dekker's | Bakery |
|---|---|---|---|
| Max Processes | 2 | 2 | N (unlimited) |
| Shared Variables | flag[2] + turn | want[2] + favoured | choosing[N] + number[N] |
| Fairness | Fair (alternating) | Fair (favoured rotation) | Strictly FCFS (ticket order) |
| Starvation Free | Yes | Yes | Yes |
| Overhead | Low (constant) | Low (constant) | Higher (O(N) scan per entry) |
| Historical Note | 1981, Gary Peterson | 1965, Th. J. Dekker (first ever) | 1974, Leslie Lamport |
The Bakery Algorithm is one of those solutions that has held up well over time. It was designed in 1974 and is still a go-to reference for understanding how mutual exclusion can be achieved purely in software for multiple processes. For systems where you need fairness, no starvation, and the ability to scale beyond two processes, it covers all of those bases cleanly.
Bakery Algorithm Scenario
Question 1 of 1Test your understanding of ticket-based synchronization.
