This commit is contained in:
161
docs/lectures/osc/05_concurrency2.md
Normal file
161
docs/lectures/osc/05_concurrency2.md
Normal file
@@ -0,0 +1,161 @@
|
||||
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 P<sub>i</sub> and P<sub>j</sub> 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**.
|
||||
|
||||
|
||||
|
||||
Reference in New Issue
Block a user