15/10/20 ## Peterson's Solution **Peterson's solution** is a **software based** solution which worked well on **older machines** Two **shared variables** are used 1. *turn* - indicates which process is next to enter its critical section 2. *Boolean flag [2]* - indicates that a process is ready to enter its critical section * Peterson's solution can be used over multiple processes or threads * Peterson's solution for two processes satisfies all **critical section requirements** (mutual exclusion, progress, fairness) `````c do { flag[i] = true; // i wants to enter critical section turn = j; // allow j to access first while (flag[j] && turn == j); // whilst j wants to access critical section // and its j’s turn, apply busy waiting // CRITICAL SECTION counter++ flag[i] = false; // remainder section } while (...); ````` **Figure**: *Peterson's solution for process i* ```c do { flag[j] = true; // j wants to enter critical section turn = i; // allow i to access first while (flag[i] && turn == i); // whilst i wants to access critical section // and its i’s turn, apply busy waiting // CRITICAL SECTION counter++ flag[j] = false; // remainder section } while (...); ``` **Figure**: *Peterson's solution for process j* Even when these two processes are interleaved, its unbreakable as there is always a check to see if the other process is in the critical section. ### Mutual exclusion requirement: The variable turn can have at most one value at a time. * Both `flag[i]` and `flag[j]` are *true* when they want to enter their critical section * Turn is a **singular variable** that can store only one value * Hence `while (flag[i] && turn == i);` or `while (flag[j] && turn == j);` is true and at most one process can enter its critical section (mutual exclusion) **Progress**: any process must be able to enter its critical section at some point in time > Processes/threads in the **remaining code** do not influence access to critical sections > > If a process *j* does not want to enter its critical section > > * `flag[j] == false` > * `white (flag[j] && turn == j)` will terminate for process *i* > * *i* enters critical section ### Fairness/bounded waiting Fairly distributed waiting times/process cannot be made to wait indefinitely. > If Pi and Pj both want to enter their critical section > > * `flag[i] == flag[j] == true` > * `turn` is either *i* or *j* assuming that `turn == i` *i* enters it's critical section > * *i* finishes critical section `flag[i] = false` and then *j* enters its critical section. Peterson's solution works when there is two or more processes. Questions on Peterson's solution with more than two solutions is not in the spec. **Disable interrupts** whilst **executing a critical section** and prevent interruptions from I/O devices etc. For example we see `counter ++` as one instruction however it is three instructions in assembly code. If there is an interrupt somewhere in the middle of these three instructions bad things happen. ```c register = counter; register = register + 1; counter = register; ``` Disabling interrupts may be appropriate on a **single CPU machine**, not on a multi-core processor though. This means multiple cores can take a value from memory, manipulate that value (in this example iterating it) whilst not knowing the value has already changed on a different core and write back the wrong value to memory. This can lead to `1+1+1=2`. ### Atomic Instructions > Implement `test_and_set()` and `swap_and_compare()` instructions as a **set of atomic (uninterruptible) instructions** > > * Reading and setting the variables is done as **one complete set of instructions** > * If `test_and_set()` / `sawp_and_compare()` are called **simultaneously** they will be executed sequentially. > > They are used in combination with **global lock variables**, assumed to be `true (1) ` is the lock is in use. #### Test_and_set() ```c // Test and set method boolean test_and_set(boolean * bIsLocked) { boolean rv = *bIsLocked; *bIsLocked = true; return rv; } // Example of using test and set method do { // WHILE the lock is in use, apply busy waiting while (test_and_set(&bIsLocked)); // Lock was false, now true // CRITICAL SECTION ... bIsLocked = false; ... // remainder section } while (...) ``` * `test_and_set()` must be **atomic**. * If two processes are using `test_and_set()` and are interleaved, it can lead to two processes going into the critical section. ```c // Compare and swap method int compare_and_swap(int * iIsLocked, int iExpected, int iNewValue) { int iTemp = *iIsLocked; if(*iIsLocked == iExpected) *iIsLocked = iNewValue; return iTemp; } // Example using compare and swap method do { // While the lock is in use (i.e. == 1), apply busy waiting while (compare_and_swap(&iIsLocked, 0, 1)); // Lock was false, now true // CRITICAL SECTION ... iIsLocked = 0; ... // remainder section } while (...); ``` `test_and_set()` and `swap_and_compare()` are **hardware instructions** and **not directly accessible** to the user. **Disadvantages**: * **Busy waiting** is used. When the process is doing **nothing** just sitting in a loop, the process is still eating up processor time. If I know the process won't be **waiting for long busy waiting is beneficial** however if it is a long time a blocking signal will be sent to the process. * **Deadlock** is possible e.g when two locks are requested in opposite orders in different threads. The OS uses the hardware instructions to implement higher level mechanisms/instructions for mutual exclusion i.e. **mutexes** and **semaphores**.