---
# System prepended metadata

title: Process Synchronization

---

# Process Synchronization(同步)
## Background
* 並發訪問共享數據可能會導致數據不一致
* 維護數據一致性需要機制來保證協作process的有序執行

## Consumer & Producer 問題
* 判斷緩衝區是空還是滿
    * 以前：使用 in、out 位置
    * 現在：使用 counter

![](https://hackmd.io/_uploads/SkwuijNon.png)

* counter + + 用機器語言實現

```
    move ax, counter
    add ax, 1
    move counter, ax
```
* counter - - 用機器語言實現

```
    move bx, counter
    sub bx, 1
    move counter, bx
```
## Instruction Interleaving

![](https://hackmd.io/_uploads/rytQ13Ei3.png)

* 這樣的穿插情形counter可能4、5、6，但實際上要是5

* 在這個示例中，由於Consumer和Producer共享了一個變數counter，且沒有 synchronized 機制，可能會導致**race condition**，進而產生錯誤的結果。

* 要解決這個問題，需要使用 synchronized 機制來確保Consumer和Producer對共享變數的存取是安全的，避免Race Condition和未預期的行為。

**Race Condition**：Consumer和Producer同時訪問 counter 變數，導致不正確的計數。在 producer 還沒有更新 counter 之前，consumer 就可能已經讀取了不正確的計數值。這會導致資源共享的不一致性。

## Race Condition

Race Condition：multi-thread 或 multi-process 環境中常見的一種情況，當兩個或多個thread（或process）同時訪問共享資源，且至少有一個thread（或process）在修改資源的情況下，可能導致不可預測的結果。

Race Condition 可能導致程式的行為不符合預期，甚至可能引發錯誤或不穩定的情況。

共享數據的最終值取決於哪個process最後完成

* 為了防止Race Condition，並發process必須 synchronized
    * 在單 processor 機器上，我們可以禁用 interrupt 或使用non-preemptive CPU scheduling
* 通常被描述為 **Critical-Section Problem**

## The Critical-Section Problem
* 目的：processes 合作的協議
* 問題描述：
    * N個 processes 正在競爭使用一些共享數據 
    * 每個 process 都有一個代碼段，稱為critical section ，在其中訪問共享數據
    * 確保當一個 process 在其critical section 執行時，不允許其他 processes 在其critical section 執行
    * 一般常見 critical section 的架構如下 
    
![](https://hackmd.io/_uploads/S1B7X24i2.png)

`entry section`： process 開始執行，進入這個區域。在進入 CS 之前， process 會在這個區域進行某些設置和準備工作。

`critical section`：process 在這個區域執行需要保護的操作，確保不會與其他 process 同時進入該區域，即需要互斥訪問的共享資源。

`exit section`： process 在執行完 CS 的操作後，進入這個區域。在離開 CS 之前， process 會進行某些清理和恢復工作。

`remainder section`： process 在離開 CS 後，進入這個區域。在這個區域 process 可以執行其他非關鍵操作，例如等待下一次進入 CS 的機會。

## Critical Section Requirements

**Critical Section(CS)**：是指在 multi-thread 或 multi-process 環境中，需要保護的一段程式碼區域，該區域訪問共享資源，且在同一時間只能有一個 thread （或 process）進入，以避免 Race Condition 和確保資源的正確訪問。

Critical Section 通常是對共享變數、資料結構或共享資源進行讀取、寫入、更新等操作的程式碼片段。

1. mutually exclusive：在某個時間點只能有一個process或thread訪問共享資源。
    
    * 例如：如果process P 在其 CS 中執行，則其他 process 不能在其 CS 中執行

2. Progress：是指系統中的每個執行體都能夠在合理的時間內進展，而不會無限期地被阻塞。
    
    * 例如：如果其 CS 中沒有process正在執行，並且存在一些希望enter其 CS 的process，則這些process不能無限期推遲

3. Bounded Waiting：是指在獲取共享資源時，執行體等待的次數是有限的。這是為了防止某個執行體無限期地被其他執行體擱置，從而保證所有執行體都有機會訪問共享資源。
    * 例如：在process請求enter其 CS 後，其他process被允許 enter其 CS 的次數必須存在限制

### Algorithm for Two Processes

* 只有 2 個 process，P<sub>0</sub> 和 P<sub>1</sub>
* 共享變數
    * int turn； // initially turn = 0
    * turn = i ⇒ P<sub>i</sub>，可以enter其 Critical Section

![](https://hackmd.io/_uploads/BJsxLnEj3.png)

對於P<sub>0</sub>
```
while (1) {
    while (turn == 1);  // 等待 P1 的回合結束
    // 進入 P0 的CS
    // 執行CS的程式碼
    turn = 1;  // 將 turn 設為 1，讓 P1 進入CS
    // 離開CS
}

```
對於P<sub>1</sub>
```
while (1) {
    while (turn == 0);  // 等待 P0 的回合結束
    // 進入 P1 的 CS
    // 執行 CS 的程式碼
    turn = 0;  // 將 turn 設為 0，讓 P0 進入 CS
    // 離開 CS
}

```
## Proof of Peterson's Solution
1. mutually exclusive：
    
    如果 P<sub>0</sub>，CS → `flag[1]== false || turn == 0`
    如果 P<sub>1</sub>，CS → `flag[0] == false || turn == 1`
    * 假設兩個 processes 都在CS → `flag[0] == flag[1] == true`
        → turn == 0 為 P<sub>0</sub> enter，turn==1 為 P<sub>1</sub> enter
        * 然而，“turn” 將為 0 或 1，因為它的值將為兩個 processes 設置，但只有一個值會持續
        * 所以，P<sub>0</sub>，P<sub>1</sub>，不能同時在CS！

![](https://hackmd.io/_uploads/Hk_dpHPj3.png)

* 當其中一個 processor （例如 P0）進入 CS 時，另一個 processor （例如 P1）不能同時進入 CS 。

* 這樣可以確保在任何給定的時間，只有一個 processor 能夠在 CS 內執行，從而避免 Race Codition 和不一致性。

* 通過適當的 Synchronization ，可以實現這種 mutually exclusive，以確保資源的正確共享和操作。

接上圖

2. Progress （例如，P<sub>0</sub> wishes to enter its CS): 

    1. 如果 P<sub>0</sub> 未就緒flag[1] = false → P<sub>0</sub> 可以 enter  
    2. 如果兩者都就緒 flag[0] = flag[1] == true 如果 trun = 0 則 P<sub>0</sub>  enter ，否則 P<sub>1</sub>  enter 
    > 無論哪種情況，某些 waiting process 都可以 enter CS！

* 確保"Progress" 是設計和實現 Synchronizatio n時的重要目標之一。它確保了系統的公平性和正常運行，避免了一些可能導致系統無法進行下去的極端情況。

3. Bounded waiting (e.g., P<sub>0</sub> wishes to enter its CS):
    
    1. 一旦 P<sub>1</sub> 退出CS → flag[1]==false →  P<sub>0</sub> 可以 enter  
    2. 如果 P<sub>1</sub> 退出CS && 重置 flag[1]=true
        →turn==0 （覆蓋 P<sub>0</sub> 設置）→ P<sub>0</sub> 可 enter 
    > P<sub>0</sub>不會無限期地等待！

## Bakery Algorithm (n processes)

核心思想是，每個thread或 processor 都被賦予一個號碼（類似於取號的方式），並且在進入臨界區之前，它們必須等待號碼輪到自己。

當號碼比較小的thread或 processor 可以進入 CS 時，其他thread則必須等待。

*  在進入其CS 之前，每個 process 都會收到一個#
*  擁有最小#的人進入CS
*  編號方案始終以非降序生成#； 即 1,2,3,3,4,5,5,5
*  如果 process  P 和 P, 收到相同的 #，如果 i < j，則首先為 P, 提供服務

符號：
> (a, b) < (c, d) 如果 a < c 或如果 a ==c && b < d

![](https://hackmd.io/_uploads/ByBz-8vih.png)

* 為什麼修改num時無法比較？
* Without locking...

1. 設5為當前最大數
2. 如果P1和P4一起取數，但P4先於P1完成
>P1=0； P4=6， P4將進入CS
3. P1取號後
> P1-P4-6 P1也將進入CS！！！

* With locking...
> P4必須等待P1取完號
>> P1和P4在比較之前都會有新的數字“6”

## Pthread Lock/Mutex Routines

Mutex 的主要目的是確保同一時間內只有一個 thread 能夠訪問被鎖住的區域（CS），從而避免race codition 和確保資源的一致性。

使用 Mutex 時需要謹慎，確保在合適的地方加鎖和解鎖，以避免 deadlock 和其他同步問題。

* 要使用mutex，必須將其聲明為 pthread_mutex_t 類型並使用 pthread_mutex_init() 進行初始化
* 使用 pthread_mutex_destory() 銷毀 mutex
* 然後可以使用 pthread_mutex_lock() 和 pthread_mutex_unlock() 保護臨界區

![](https://hackmd.io/_uploads/S1R7FUDj3.png)

## Condition Variables (CV)

它們通常與 Mutex 一起使用，以實現更複雜的同步需求，例如等待特定條件的滿足或通知其他 thread 發生了某些事件。

* Condition Variables 代表一個條件，符合該條件時可以觸發 thread 執行某些動作
* 三種 CV 的操作
    * wait() : 直到其他 thread 呼叫 signal()/broadcast()，thread 處於等待狀態
    * signal() : 喚醒一個等待中的 thread
    * broadcast() : 喚醒所有等待中的 thread
* 在 Pthread 中，CV type 為 `pthread_cond_t`
    * 使用 `pthread_cond_init()` 來初始化
    * `pthread_cond_wait (&theCV, &somelock)`
    * `pthread_cond_signal (&theCV)`
    * `pthread_cond_broadcast (&theCV)`

![](https://hackmd.io/_uploads/SyP3xsdj2.png)

## ThreadPool Implementation

* ThreadPool 是一種 multi-thread 技術，用於管理和重複使用 thread ，以處理多個任務。它可以提高應用程式的效率和性能，並允許在一個固定的 thread 集合中執行多個任務，而不需要為每個任務都創建一個新的 thread 。

![](https://hackmd.io/_uploads/Bygblytsh.png)

`void (*function)(void)`：這是一個指向函數的指針，表示要執行的任務。這個函數不接受任何參數，也不返回任何值。這種指針可用於存儲任意的函數，以供 ThreadPool 執行。
`void *argument`：這是一個指向 void 的指針，用於存儲傳遞給任務函數的參數。由於這是一個通用的指針，可以用於傳遞任何類型的數據。

![](https://hackmd.io/_uploads/HJKgx1Ko3.png)

`pthread_mutex_t lock`：一個 mutex ，用於保護 ThreadPool 的訪問，確保同一時間只有一個 thread 可以修改 ThreadPool 的狀態。

`pthread_cond_t notify`：一個 CV ，用於通知等待在 ThreadPool 上的 thread ，當有新任務加入或 ThreadPool 關閉時，可以使用這個 CV 進行通知。

`pthread_t threads`：一個 thread 陣列，用於存放 ThreadPool 中的 thread 。

`threadpool_task_t queue`：一個存放任務的陣列，用於存放提交給 ThreadPool 的任務。

`int thread_count`：表示 ThreadPool 中的 thread 數量。

`int queue_size`：表示任務 queue 的大小。

`int head` 和 `int tail`：分別表示任務 queue 的頭部和尾部索引，用於管理 queue 中的任務。

`int count`：表示當前任務 queue 中的任務數量。

`int shutdown`：標誌位，表示 ThreadPool 是否已關閉。

`int started`：標誌位，表示 ThreadPool 是否已啟動。

![](https://hackmd.io/_uploads/r12Mxktj2.png)![](https://hackmd.io/_uploads/ryfXeJtih.png)![](https://hackmd.io/_uploads/SkOXlyKj3.png)

`static void *threadpool_thread(void *threadpool)`：這是一個 thread 函數，接受一個指向 ThreadPool 結構的指標 threadpool 作為參數。它的返回值是 `void *`，因為 thread 函數的返回值通常是 `void *`。

`threadpool_t *pool = (threadpool_t *)threadpool;`：將 threadpool 轉換為 threadpool_t 結構的指針，以便在 thread 函數中使用 ThreadPool 的成員。

`threadpool_task_t task;`：創建一個 threadpool_task_t 變數，用於存放要執行的任務。

進入無限循環 `for(;;)`，表示 thread 會一直執行。

`pthread_mutex_lock(&(pool->lock));`：鎖定 mutex ，以確保 thread 安全。

`while((pool->count == 0) && (pool->shutdown))`：在等待條件變數上等待，直到有任務可執行或  ThreadPool 被關閉。如果 queue 中沒有任務且  ThreadPool 被關閉，則等待。

從任務 queue 中取出一個任務，並將相關資訊複製到 `task` 變數。

更新 queue 的頭部索引，並減少 queue 中任務的數量。

`pthread_mutex_unlock(&(pool->lock));`：解鎖 mutex 。

執行任務的函數，並傳遞任務的參數。

## Atomic Test And Set 
atomic：不能被interrupt。

Atomic Test And Set 是一個用於確保多個 thread 或多個 process 在並發情況下安全地執行操作的同步 atomic 操作。

它的主要目的是確保在進行某些操作時，只有一個 thread 或 process 能夠成功地設置一個共享的 boolean 變數，避免了 race condition。

![](https://hackmd.io/_uploads/SyNROxcj2.png)

* `TestAndSet` 函數的實現與之前的描述一致。它確保在同一時間只有一個 process 可以將 `lock` 設置為 `TRUE`，並返回之前的狀態。

* P0 和 P1 兩個 process 的主循環，都包含一個 while 循環，用來等待 `lock` 變為 `FALSE`，以確保其他 process 已經離開 CS 。這個循環稱為 busy-waiting ，因為它會持續檢查 `lock` 直到可以進入 CS 為止。
 
* 一旦 `TestAndSet(lock)` 返回 `FALSE`，表示 CS 目前未被其他 process 佔用，當前 process 可以進入 CS ，執行一些關鍵的操作。
 
* 在 CS 的操作完成後，將 `lock` 設置為 `FALSE`，表示 CS 已經釋放，其他 process 可以進入。
 
* `remainder section` 是在 CS 之外執行的一些其他操作。
 
* `while (1)`; 是一個無限循環，確保這兩個 process 持續地進行上述步驟。
 
雖然這段程式碼確保了 CS 的互斥同步，但它存在一個問題，

在這個程式碼中，每個 process 使用 busy-waiting 方式來等待 `lock` 變為 `FALSE`，以進入 CS 。這種方式可能導致一個 process 在等待時無法進入 CS ，而其他 process 持續地訪問 lock，導致無法 Bounded Waiting 。
## Atomic Swap 
Atomic Swap 是一種同步 Atomic 操作，用於交換兩個變數的值，同時保證操作是 Atomic，不會被中斷。這種操作在多 thread 或多 process 環境中特別有用，可以確保交換的過程不會受到 race condition 的影響。

![](https://hackmd.io/_uploads/B1hyafqo2.png)

* 在 `do { ` 循環中，P0 process 首先將 `key0` 設置為 `TRUE`，表示它希望進入 CS 。然後，它進入一個 `while` 循環，只有在 `key0` 變為 `FALSE` 時才會退出，這就意味著只有在另一個 process 將 `key0` 設置為 `FALSE` 時，P0 才會繼續執行。

* 在 `Swap (lock, key0);` 操作中，`P0` 使用 Atomic Swap 交換 `lock` 和 `key0` 的值。這確保了操作的 Atomic，使得只有一個 process 可以成功進行 Swap 操作。

*　一旦 `key0` 變為 `FALSE`，表示 P0 可以進入 CS ，並執行關鍵的操作。

* 在關鍵區域完成後，P0 將 `lock` 設置為 `FALSE`，表示 CS 已經釋放。

* `while (1);` 是一個無限循環，確保 P0 持續地進行上述步驟。

* P1 process 的部分也是類似的，用來實現與 P0 相同的互斥同步機制。
## Semaphores
* Samaphores 是一個通用的同步處理工具，其紀錄某個資源有多少 units 可以去使用
    * 若 # of record = 1 --> binary semaphore, mutex lock
    * 若 # of record > 1 --> counting semaphore
* 利用兩個 atomic 函數 wait & signal 來存取 

![](https://hackmd.io/_uploads/BkF8fm5j3.png)

* wait(S)：當一個 process 或 thread 需要訪問共享資源時，它會執行 wait(S) 操作。如果信號量的值大於零，表示資源可用，該 process 可以繼續執行並減少信號量的值。如果信號量的值為零或負值，表示資源已經被佔用， process 需要等待。

* signal(S)：當一個 process 或 thread 完成對共享資源的訪問時，它會執行 signal(S) 操作，將信號量的值增加。這表示資源已經被釋放，其他 process 可以進行訪問。

## Non-busy waiting Implementation

* Semaphore 是一個有 queue 的結構體，包含 Semaphore 的值以及有哪些 Processes 等待被執行

![](https://hackmd.io/_uploads/By3nv75sh.png)

`int value`：這是信號量的計數值。在初始化時，它被設置為 0。計數型信號量可以有正值、零值和負值，表示資源的可用性狀態。

`struct process *L`：這是一個指向 process （或 PCB，Process Control Block）隊列的指針。該隊列用於存儲等待中的 process ，即等待資源的 process 。當資源不可用時， process 將被加入到此隊列中。

* wait() 和 signal() 
    * 使用系統調用：block() 和wakeup() 
    * 必須以 atomically execute

![](https://hackmd.io/_uploads/SJJ5dQ5i3.png)

**這種方式使用了「sleep 和 wakeup 」的機制。當資源不可用時， process 會進入睡眠狀態，等待資源的釋放。這樣可以避免浪費CPU資源，因為 process 只會在資源可用時被喚醒，而不會持續檢查。**

## Cooperation Synchronization
* P1執行S1； P2執行S2
    * S2僅在S1完成後執行
* Implementation:
    * shared var:
        * semaphore sync; // initially sync = 0

![](https://hackmd.io/_uploads/H10RHV5s3.png)

常見的問題如下：

1. Bounded-Buffer (Producer-Consumer) Problem
2. Reader-Writers Problem
3. Dining-Philosopher Problem

## Reader-Writers Problem

問題的目標是確保對共享資源的訪問是安全和有效的，同時保證 reader 之間可以同時訪問，而 writer 之間和 reader 之間必須進行互斥。

* 標準在處理 I/O 會遇到的問題
* 一組共享數據對象
    * 一組process
    * reader process（讀取共享對象）
    * writer process（更新共享對象）

* 涉及優先級的不同變化
    * first RW problem : 除非 writer 正在更新共享對象，否則 reader 不會一直等待
        * 只要確定 writer 有 access 的獨佔權即可，不關心 reader 要等多久，或要做什麼事情。可能造成 writer 一直被其他 reader 插隊，永遠無法獲取訪問的機會
    * second RW problem : 一旦 writer 準備好，它就會在共享對像被釋放後立即執行更新
        * 不只要確定 writer 的 access 權限，另外當 writer 在準備好且訪問時，writer 的優先性必須高於 reader ，不可有新的 reader 進來插隊
#### First Reader-Writer Algorithm
```
// mutual exclusion for write 
semaphore wrt=1
// mutual exclusion for readcount
semaphore mutex=1
int readcount=0;

// writer
Writer(){
    while(TRUE){
        wait(wrt); // 等待 writer 信號量，保證只有一個 writer 能夠訪問
        // Writer Code（ writer 的操作在這裡）
        signal(wrt); // 釋放 writer 信號量，允許其他 writer 訪問
    }
}

// reader
Reader(){
    while(TRUE){
        wait(mutex); // 等待互斥鎖，保證 reader 數量的操作是原子的
        readcount++;
        if(readcount == 1)
            wait(wrt); // 當第一個 reader 進入時，等待 writer 信號量，阻止 writer 訪問
        signal(mutex); // 釋放互斥鎖

        // Reader Code（ reader 的操作在這裡）

        wait(mutex); // 等待互斥鎖，保證 reader 數量的操作是原子的
        readcount--;
        if(readcount == 0)
            signal(wrt); // 當最後一個 reader 離開時，釋放 writer 信號量，允許 writer 訪問
        signal(mutex); // 釋放互斥鎖
    }
}

```
*  writer 部分：
    * 進入 writer 代碼區域前，等待 writer 信號量 wrt。
    * 進入 writer 代碼區域，執行 writer 的操作。
    * 完成 writer 操作後，釋放 writer 信號量 wrt，允許其他 writer 進入。
*  reader 部分：
    * 進入 reader 代碼區域前，等待互斥鎖 mutex。
    * 增加 reader 數量（readcount）。
    * 如果是第一個 reader 進入，則等待 writer 信號量 wrt，阻止 writer 訪問。
    * 釋放互斥鎖 mutex，允許其他 reader 進入。
    * 執行 reader 的操作（在 "Reader Code" 區域內）。
    * 進入 reader 代碼區域前，再次等待互斥鎖 mutex。
    * 減少 reader 數量。
    * 如果是最後一個 reader 離開，則釋放 writer 信號量 wrt，允許 writer 訪問。
    * 釋放互斥鎖 mutex。

* 透過信號量 wrt， writer 確保同一時間只有一個 writer 能夠進行寫操作，以避免 race condition 。
* 透過信號量 mutex，確保 reader 數量的操作是 atomic ，避免多個 reader 同時修改 readcount。
* 當第一個 reader 進入時，它會等待 wrt 信號量，從而阻止 writer 進行訪問，確保 reader - writer 優先策略。
* 當最後一個 reader 離開時，它會釋放 wrt 信號量，允許 writer 進行訪問，確保 writer 的訪問權限。

## Dining-Philosopher Problem

* 5 個人坐在 5 張椅子上，且有 5 根筷子
* 每個人只會思考或是吃飯
    * 思考時與剩下 4 個人沒有互動
    * 每個人要用餐需要兩根筷子
    * 一個人 1 次只能拿 1 根筷子
    * 吃完飯後，將兩根筷子都放下
    
* deadlock problem
> 一根筷子作為一根信號量
* starvation problem

![](https://hackmd.io/_uploads/B1ibHyni3.png)

## Monitor
是一種用於同步多線程或多 process 程序的高階同步機制。它提供了一種結構化的方式來組織共享資源的訪問，以避免 race condition、 deadlock 和其他同步問題。

動機：
* 雖然信號量提供了方便有效的同步機制，但其正確性取決於程序員
    
    * 所有訪問共享數據對象的 process 必須以正確的順序和正確的位置執行 wait() 和 signal()
> 這可能不是真的，因為誠實的編程錯誤或不合作的程序員
>>"誠實的程式設計錯誤" 是指由於程式設計師無意中犯下的錯誤，而不是故意地惡意操作程式碼。

![](https://hackmd.io/_uploads/B1tIStaoh.png)

### Monitor --- 高級語言構造
* monitor type的表示包括
    * declarations of variables，其值定義類型實例的狀態
    * 實現類型操作的過程/函數
* monitor type 類似於OOP語言中的 class
    * monitor 內的過程只能訪問局部變量和形式參數
    * monitor 的局部變量只能由局部過程使用
* 但是，monitor 確保 monitor 內一次只能有一個 process 處於活動狀態
* 類似的想法被納入許多程序中。 
* 語言：並發 pascal、C# 和 Java

### Monitor Condition Variables
* 為了允許 process 在 monitor 內等待，必須聲明一個條件變量，如下所示條件x，y；
* 條件變量只能與 wait() 和 signal() 操作一起使用
    * x.wait(); 意味著調用此操作的 process 被掛起，直到另一個 process 調用
    * x.signal(); 恰好恢復一個掛起的 process。如果沒有 process 被掛起，那麼signal操作沒有效果
（相反，signal 總是改變 semaphores 的狀態）

## Dining Philosophers Example
```
monitor dp {
    enum {thinking, hungry, eating} state[5]; // 當前狀態
    condition self[5]; // 如果不能獲取筷子，延遲吃飯
    void pickup(int i) // 拿起筷子
    void putdown(int i) // 放下筷子
    void test(int i) // 嘗試進食
    void init() {
        for (int i = 0; i < 5; i++)
            state[i] = thinking;
    }
}

void pickup(int i) {                
    state[i] = hungry;                  
    test(i); // 嘗試進食               
    if (state[i] != eating)                  
        // 等待進食                   
        self[i].wait();             
}            

void putdown(int i) {
    state[i] = thinking;
    // 檢查鄰座是否在等待進食
    test((i+4) % 5); 
    test((i+1) % 5);
}
// 嘗試讓 Pi 吃東西（如果它餓了）
void test(int i) {
    if ( (state[(i + 4) % 5] != eating) &&(state[(i + 1) % 5] != eating) 
        && (state[i] == hungry) ) {
    // 沒有鄰居在吃飯，Pi 餓了
        state[i] = eating;
        self[i].signal(); 
    // 如果 Pi waited，則恢復它
  　// 如果 Pi 沒有 waited，則沒有影響
    }
} 
```
這段程式碼是關於經典的「Dining-Philosopher Problem」的一種解法，使用了一個 monitor 來實現。

* enum {thinking, hungry, eating} state[5];：定義了一個 enum 來表示哲學家的三種狀態

* void pickup(int i)：這是當一位哲學家想要拿起筷子時呼叫的函數。

* state[i] = hungry：將該哲學家的狀態設置為饑餓。
    * test(i)：調用 test 函數來嘗試進餐。
    * if (state[i] != eating)：如果哲學家不能進餐（因為他的鄰座哲學家正在進餐），則進入等待狀態。
    * self[i].wait();：通過等待 self[i] 的條件變數，暫停該哲學家的執行，直到被其他哲學家喚醒。
    * void putdown(int i)：這是當一位哲學家放下筷子時呼叫的函數。

* state[i] = thinking：將該哲學家的狀態設置為思考。
    * test((i+4) % 5); 和 test((i+1) % 5);：調用 test 函數來檢查左右鄰座的哲學家是否可以進餐，從而喚醒可能在等待的哲學家。
    * void test(int i)：這個函數用於檢查一位哲學家是否可以進餐，如果可以，則讓他進餐。

* state[i] = eating;：將該哲學家的狀態設置為進食。
    * test(int i)檢查相鄰的兩位哲學家是否都不在進食，並且該哲學家處於 "hungry" 狀態
    * 如果滿足條件，將該哲學家的狀態設為 "eating"，並通過 self[i].signal() 喚醒等待的哲學家。

## Atomic Transactions
### System Model
* Transactions：執行單個邏輯功能的指令（或多個指令）的集合
* Atomic Transactions：操作作為單個邏輯工作單元發生，可以完全發生，也可以根本不發生
* Atomic Transactions 是數據庫系統特別關注的問題
    * 對在 OS 中使用數據庫技術有濃厚的興趣

### File I/O 示例

* transaction 是一系列的 read and write操作
* 由 commit （transaction successful）或 abort （transaction failed）使操作終止 
* Aborted transaction 必須 rolled back 以撤消其執行的任何更改
    * 確保該屬性是系統責任的一部分

### Log-Based Recovery
* 記錄有關 transaction 所有修改的 stable storage 信息
    
    * Stable storage：存儲數據永不丟失
* Write-ahead logging：每條日誌記錄描述transaction 寫入操作
    * Transaction name
    * Data item name
    * Old & new values
    *  Special events: <T<sub>i</sub> starts>, <T<sub>i</sub> commits>
* Log 用來重建被改動資料的原始狀態
    * 利用 undo(Ti) 及 redo(Ti) 來復原資料
### Checkpoints
* 發生故障時，必須查閱日誌以確定必須重新執行哪些事務
    
    * 搜索過程非常耗時
    * 並非所有交易都需要重做
* 使用 checkpoints 來減少上述開銷
    * 將所有日誌記錄輸出到 stable storage
    * 將所有修改數據輸出到 stable storage
    * 輸出一條日誌記錄 < checkpoint >到 stable storage
