In Peterson's Algorithm, a process switches the turn
variable into the other process.
However, I'm wondering why we don't switch the turn variable into the current process, like this:
void process0() {
flag[0] = 1; // Process 0 wants to access the critical section
turn = 0; // It's Process 0's turn to access the critical section
while (flag[1] && turn == 1); // If Process 1 wants to access the critical section and it IS Process 1's turn, then Process 0 should wait
// Process 0 accesses the critical section
flag[0] = 0; // Process 0 quits the critical section
// Remaining codes
}
void process1() {
flag[1] = 1; // Process 1 wants to access the critical section
turn = 1; // It's Process 1's turn to access the critical section
while (flag[0] && turn == 0); // If Process 0 wants to access the critical section and it IS Process 0's turn, then Process 1 should wait
// Process 1 accesses the critical section
flag[1] = 0; // Process 1 quits the critical section
// Remaining codes
}
It seems that there is always a process that is able to access the critical section and there's only one process that can access the critical section, as well. So why don't we switch the turn variable into the current process number?
I have tried using this python code:
import threading, random
END_POSSIBILITY = 0.001 # The possibility that one process finishes running
flag = [False, False]
turn = 0
class CriticalSection:
def __init__(self) -> None:
self.visitor = -1
def visit(self, visitor: int) -> None:
if self.visitor == -1:
self.visitor = visitor
else:
raise Exception(f"CRITICAL SECTION IS ALREADY BEING VISITED BY PROCESS {self.visitor}, CANNOT BE VISITED BY {visitor}!!!")
def unvisit(self) -> None:
self.visitor = -1
critical_section = CriticalSection()
def process0():
global turn
while True:
flag[0] = True
turn = 0
while flag[1] and turn == 1:
pass
critical_section.visit(0)
flag[0] = False
critical_section.unvisit()
if (random.random() <= END_POSSIBILITY):
break
def process1():
global turn
while True:
flag[1] = True
turn = 1
while flag[0] and turn == 0:
pass
critical_section.visit(1)
flag[1] = False
critical_section.unvisit()
if (random.random() <= END_POSSIBILITY):
break
thread0 = threading.Thread(target=process0)
thread1 = threading.Thread(target=process1)
thread0.start()
thread1.start()
And I ran this code many times and everything was normal.
Suppose the operations get sequenced as follows.
P2 stalls while P1 executes:
flag[0] = 1;
turn = 0;
while (flag[1] && turn == 1); // terminates immediately because turn == 0
// critical section
And while P1 is in the critical section, P2 begins executing:
flag[1] = 1;
turn = 1;
while (flag[0] && turn == 0); // terminates immediately because turn == 1
// critical section
Now both processes are in the critical section simultaneously.
Essentially, in your version, either process can declare it to be its own turn now, while completely ignoring whether the other process is in the middle of the critical section.
In the original Peterson algorithm, by having P0 set turn = 1
and then wait until turn == 0
again, it ensures that P0 doesn't continue until P1 has given positive acknowledgment that its turn is complete. In your version, P0 can proceed without any kind of response from P1.
And I ran this code many times and everything was normal.
In trying to reproduce race conditions, it's often not very helpful to run everything at full speed. Rather, add delays at spots that are potentially sensitive.
I modified your code by adding time.sleep(5)
inside the critical section in process0
, and time.sleep(1)
at the very beginning of process1()
, and the exception is triggered.