Critical Section Problem Solution
1. Objectives
• To understand the concept of the Critical Section problem in concurrent programming.
• To implement Peterson’s Algorithm as a solution to the Critical Section problem for
two processes.
2. Theory
The Critical Section Problem
In concurrent programming, multiple processes or threads often need to access shared re-
sources, such as a common variable, a file, or a database. The portion of the program where
a process accesses these shared resources is known as the Critical Section.
The Critical Section Problem is the challenge of designing a protocol that processes
can use to cooperate. The goal is to ensure that when one process is executing in its critical
section, no other process is allowed to execute in its critical section. This prevents data
inconsistencies and race conditions that can arise from simultaneous access and modification
of shared data.
A valid solution to the Critical Section Problem must satisfy the following three condi-
tions:
1. Mutual Exclusion: If a process is executing in its critical section, then no other
processes can be executing in their critical sections. This is the most crucial property.
2. Progress: If no process is currently in its critical section and some processes wish to
enter their critical sections, then the selection of the next process to enter its critical
section cannot be postponed indefinitely. This means the system cannot deadlock.
3. Bounded Waiting (or Bounded Fairness): There must be a limit on the number
of times that other processes are allowed to enter their critical sections after a process
has made a request to enter its critical section and before that request is granted. This
ensures that no process has to wait forever (starvation).
1
Peterson’s Algorithm
Peterson’s Algorithm is a classic software-based solution to the critical section problem that
works for two processes. It elegantly uses shared memory for coordination. The algorithm
uses two shared variables:
• int turn;: An integer variable that indicates whose turn it is to enter the critical
section. If turn == 0, it’s process P0 ’s turn; if turn == 1, it’s process P1 ’s turn.
• boolean flag[2];: A boolean array where flag[i] = true indicates that process Pi
is ready to enter its critical section.
A process Pi signals its readiness by setting flag[i] to true and then yields the turn to
the other process (turn = j). It then waits in a while loop as long as the other process is also
ready (flag[j] == true) and it is the other process’s turn (turn == j). This combination
of flag and turn ensures that the three conditions for a solution are met.
3. Algorithm
Peterson’s Algorithm for two processes (P0 and P1) works as follows:
Shared Variables
• flag[0] and flag[1]: Boolean variables initialized to false, indicating whether each
process wants to enter its critical section.
• turn: An integer (0 or 1) indicating which process has priority to enter the critical
section.
Process P0
1. Set flag[0] = true to indicate P0 wants to enter.
2. Set turn = 1 to give P1 a chance to proceed if it wants to.
3. Wait in a loop while (flag[1] && turn == 1) until P1 is not in or does not want
the critical section.
4. Enter the critical section.
5. Set flag[0] = false to exit.
2
Process P1
1. Set flag[1] = true to indicate P1 wants to enter.
2. Set turn = 0 to give P0 a chance to proceed if it wants to.
3. Wait in a loop while (flag[0] && turn == 0) until P0 is not in or does not want
the critical section.
4. Enter the critical section.
5. Set flag[1] = false to exit.
4. Source Code
Below is a Java implementation of Peterson’s Algorithm using two threads to simulate the
two processes. The volatile keyword ensures that changes to shared variables are visible
across threads.
PetersonAlgorithm.java
1 class PetersonAlgorithm {
2 private static volatile boolean[] flag = new boolean[2];
3 private static volatile int turn = 0;
4 private static int sharedResource = 0;
5
6 static class Process extends Thread {
7 private int processId;
8 private int otherProcess;
9
10 public Process(int id) {
11 this.processId = id;
12 this.otherProcess = 1 - id;
13 }
14
15 @Override
16 public void run() {
17 for (int i = 0; i < 3; i++) {
18 try {
3
19 // Entry section - Peterson's Algorithm
20 flag[processId] = true;
21 turn = otherProcess;
22
23 // Busy wait
24 while (flag[otherProcess] && turn == otherProcess) {
25 // Busy waiting
26 }
27
28 // Critical section
29 criticalSection();
30
31 // Exit section
32 flag[processId] = false;
33
34 // Remainder section
35 remainderSection();
36
37 } catch (InterruptedException e) {
38 e.printStackTrace();
39 }
40 }
41 }
42
43 private void criticalSection() throws InterruptedException {
44 System.out.println("Process " + processId +
45 " entering critical section");
46
47 // Simulate critical section work
48 int localCopy = sharedResource;
49 Thread.sleep(100); // Simulate some processing time
50 localCopy++;
51 sharedResource = localCopy;
52
53 System.out.println("Process " + processId +
4
54 " in critical section. Shared resource = " + sharedResource);
55 Thread.sleep(100); // Simulate more processing
56
57 System.out.println("Process " + processId +
58 " leaving critical section");
59 }
60
61 private void remainderSection() throws InterruptedException {
62 System.out.println("Process " + processId +
63 " in remainder section");
64 Thread.sleep(200); // Simulate remainder section work
65 }
66 }
67
68 public static void main(String[] args) throws InterruptedException {
69 System.out.println("=== Peterson's Algorithm Demonstration ===");
70 System.out.println("Initial shared resource value: "
71 + sharedResource);
72 System.out.println();
73
74 // Create two processes
75 Process process0 = new Process(0);
76 Process process1 = new Process(1);
77
78 // Start both processes
79 process0.start();
80 process1.start();
81
82 // Wait for both processes to complete
83 process0.join();
84 process1.join();
85
86 System.out.println();
87 System.out.println("Final shared resource value: "
88 + sharedResource);
5
89 System.out.println("Expected value: 6 (3 increments from each
90 process)");
91 }
92 }
5. Output
6. Discussion
In this lab, we saw that Peterson’s Algorithm successfully solves the critical section prob-
lem for two processes by satisfying all three fundamental requirements: mutual exclusion,
progress and bounded waiting. The algorithm achieves mutual exclusion through the clever
combination of the flag array and turn variable, ensuring only one process can enter its crit-
ical section at any time. The implementation demonstrates how the algorithm prevents race
conditions that would otherwise corrupt shared data when multiple threads access resources
6
concurrently. Without Peterson’s Algorithm, race conditions occur frequently, leading to in-
correct final values in the shared counter. With the algorithm, the shared resource is always
accessed safely, demonstrating the effectiveness of the synchronization mechanism.
7. Conclusion
In conclusion, this lab successfully demonstrated how a purely software-based approach can
coordinate two processes without requiring special hardware instructions. The Peterson’s
Algorithm effectively coordinated two threads, ensuring safe access to shared resources while
maintaining progress and bounded waiting.