# Operating System #6_1
## Race Condition
* 如果你學過計算機組織,應該知道<b>count++</b>實際上實作為:
1. R1 = count
2. R1 = R1 + 1
3. count = R1
* 而<b>count--</b>則是:
1. R2 = count
2. R2 = R2 - 1
3. count = R2
---
- 想想看,今天若有兩個Processes P1和P2,分別對同一個共享資料做了++和--,你想像中可能過程是這樣:(假設count初始化為5)
1. `R1 = count` ... R1 = 5, count = 5
2. `R1 = R1 + 1` ... R1 = 6, count = 5
3. `count = R1` ... R1 = 6, count = 6
4. `R2 = count` ... R2 = 6, count = 6
5. `R2 = R2 - 1` ... R2 = 5, count = 6
6. `count = R2` ... R2 = 5, count = 5
- 看起來似乎沒有問題。然而實際上卻可能因為中斷的關係,變成以下這樣:
1. `R1 = count` ... R1 = 5, count = 5
2. `R1 = R1 + 1` ... R1 = 6, count = 5
3. `R2 = count` ... R2 = 5, count = 5 (中斷切至P2)
4. `count = R1` ... R1 = 6, count = 6
5. `R2 = R2 - 1` ... R2 = 4, count = 6
6. `count = R2` ... R2 = 4, count = 4
- 這樣的情形造成了資料不同步的錯誤,我們稱這樣的狀況為**Race Condition**,因為兩個Processes就像是在賽跑一樣,途中有可能會彼此超前。
## Critical Section Protocol
* 為了解決這樣的問題,同步化(Synchronization)是必須的。Critical Section (CS) 是一種解決方法。
* CS的特性是,在眾多Processes中如果他們都擁有CS,那麼同一時間只能有一個Process在CS裡面。
* 注意,在執行CS的Process還是可能被中斷,CS是利用同一時間只有一個Process在使用共享資料來避免不同步的問題。
* 如果一段Code要滿足CS,必須擁有以下特性:
* Mutual Exclusion
* Progress
* Bounded Waiting
### Mutual Exclusion
* 如果已經有個Process在CS內了,不能有其他Process在CS裡面。
* 如果要你證明:先假設Pi和Pj都可以進入CS,再找出矛盾推翻。
### Progress
* 如果現在沒有Process在CS內,而且有一些Processes想要進去CS內,則他們必須在**有限時間內**決定誰要進去。
* 除了想進去的Processes能夠決定誰要進去之外,其他的Processes是無法去影響他們的決策的。
* 隱含著不會有、不能有DeadLock的要素。
* 如果要你證明:
1. 如果沒有Process在CS裡面,要進去的一定進得去。
2. 多個要進入,有限時間內進得去。
### Bounded Waiting
* 對於想進入CS的Process來說,必須要在**有限時間內**成功進入CS。
* 不能有Process永遠進不去。
* 如果有N個Processes想進入CS,則每個Process最多等待n-1次即可進入。
* 隱含著不會有starvation的要素。
* 如果要你證明:
* 找出上限次數。
## Peterson’s solution
```C++=1
do {
flag[i] = TRUE;
turn = j;
while (flag[j] && turn == j);
//critical section
flag[i] = FALSE;
//remainder section
} while (1);
```
* `flag[i]`: 代表Pi對進入CS有興趣。
* `turn`: 代表現在是誰的回合。
1. 先表示自己有興趣進入CS。
2. 將回合給對方(對方優先進入)。
3. 如果不是自己的回合,使用`while()`卡住自己。
4. 結束時將自己變成沒有興趣。
## Memory Barriers
* 有時CPU執行程式時,為了增進效率,可能會在不影響結果的前提下偷換程式碼的執行順序。
* 然而這樣可能會在多個Processes共享資源時產生不同步。
* Memory barrier指強制系統按照順序來執行。
## Compare and Swap
* Compare and Swap被定義為:
```C++=1
int compare_and_swap (int *value, int expected, int new_value){
int temp = *value;
if (*value == expected)
*value = new_value;
return temp;
}
```
* 看起來很簡單,不過他擁有一個特性,這個Function受到硬體支援,因此內部執行時**絕對不會被中斷**。
* 使用這個Function就能用幫助我們完成一些CS。
* 範例:
```C++=1
while (true) {
waiting[i] = true;
key = 1;
while (waiting[i] && key == 1)
key = compare_and_swap(&lock,0,1);
waiting[i] = false;
/* critical section */
j = (i + 1) % n;
while ((j != i) && !waiting[j])
j = (j + 1) % n;
if (j == i)
lock = 0;
else
waiting[j] = false;
/* remainder section */
}
```
## Bakery Algorithm
```C++=1
do {
Choosing[i] = TRUE;
Number[i]= Max(Number[0],…Number[n-1])+1;
Choosing[i] = FALSE;
for(j=0; j<n; j++){
while(Choosing[j]) ;
while((Number[j]!=0)&&((Number[j],j)<(Number[i],i)));
}
// critical section
Number[i]=0
// remainder section
} while (TRUE);
```
* `Choosing[0, …, n-1]`: 代表每個processes抽號碼牌了沒。
* `Number[0, …, n-1]`: 代表每個processes抽到的號碼。
---
- 藉由`Choosing[0, …, n-1]`確保每個人都抽號碼牌了才進去比較階段。
- 如果沒有`Choosing[0, …, n-1]`,可能會在比較時才有process抽號碼,導致同時有兩個一起進去CS,造成問題。
- 不保證號碼不會重複,當號碼重複時用pid來比較。
- 號碼小、pid小的優先執行。
## Atomic Variables
* Atomic Variables指該變數是原子等級的,即最小單位。當它進行`++`或`--`等運算時,不會在中途被中斷。
* 當你的共享資料只做簡單的運算,就可以直接使用Atomic Variables來避免不同步的問題了。
## Busy Waiting
* 上述幾種避免資料不同的方法,可以發現都是使用`while()`來卡住process,避免同時有多個processes進入CS。
* 然而這樣的方法其實CPU依然花費資源在無謂的迴圈中等待,這樣的等待方法我們稱作**Busy Waiting**。
* 很顯然的,我們需要更好的方法來解決同步問題。
## Semaphore
* Semaphore是一種不必Busy Waiting的同步工具。
* 它的使用方法大致上長這樣:
```C++=1
Semaphore mutex; // initialized to 1
do {
wait (mutex);
// critical Section
signal (mutex);
// remainder section
} while (TRUE);
```
* 如果process前面有`wait()`就必須等候其他process執行`signal()`來喚醒它。
* 內部的實作是遇到`wait()`時將該process搬到waiting queue中等待,`signal()`才將它搬回ready queue;如此一來就可以在排程時不選擇到明明就不會做事的process。
- 
- output is 1 2 1 2 1 2。
###### tags: `note`