系統模型與多執行緒中的死結 (System Model and Deadlock in Multithreaded Applications)
本系列文章內容參考自經典教材 Operating System Concepts, 10th Edition (Silberschatz, Galvin, Gagne)。本文對應章節:Section 8.1–8.2 System Model / Deadlock in Multithreaded Applications。
死結是什麼,為什麼重要?
在多程式環境(Multiprogramming Environment)中,多個執行緒(Thread)會同時競爭數量有限的系統資源。正常情況下,執行緒請求資源、使用資源、再釋放資源,整個系統持續向前推進。但有一種情況會讓這個循環徹底卡住:
想像四位哲學家圍坐一張圓桌,每人面前有一根筷子,每人都需要拿到左右兩根才能吃飯。假設所有人同時伸手拿起左邊的筷子,此時每人手持一根,但右邊的筷子都被鄰居拿走了。每個人都在等別人放下筷子,而每個人都不會主動放手,整桌人就這樣永遠等下去。這就是死結(Deadlock)。
更正式地說,死結是指一組執行緒中的每一個執行緒,都在等待一個只有同組其他執行緒才能觸發的事件,因此整組執行緒永遠無法繼續執行。
死結問題在現代系統中變得越來越重要,原因是多核心(Multicore)硬體的普及使得並行度(Concurrency and Parallelism)大幅提升,執行緒交錯執行的可能組合呈指數增長,死結發生的機率也隨之上升。
8.1 系統模型 (System Model)
資源的類型與實例
系統中的資源(Resource)被劃分為若干資源類型(Resource Type),每種類型包含一個或多個相同的實例(Instance)。以下是幾個具體例子:
| 資源類型 | 實例數範例 |
|---|---|
| CPU cycles | 一台有 4 顆 CPU 的機器,有 4 個實例 |
| Network Interface | 系統配置了 2 張網卡,有 2 個實例 |
| File | 每個開啟的檔案即為一個實例 |
| Mutex Lock | 每把 mutex lock 通常被視為獨立的資源類型 |
| Semaphore | 同上,每個 semaphore 獨立計算 |
「同類型的實例必須完全相同」這個條件非常關鍵:如果執行緒請求某個資源類型的任意一個實例,系統分配其中哪一個都應該能滿足需求。若不同實例之間有差異,就代表它們屬於不同的資源類型,必須重新分類。
在實務上,同步工具(Synchronization Tools)是現代系統中最常見的死結來源。每一個 mutex lock 通常被關聯到一個特定的資料結構(例如保護 queue 的鎖、保護 linked list 的鎖),因此每個 mutex lock 的實例通常被獨立歸為一個資源類型。
本章的系統模型只涵蓋由 OS kernel 親自仲裁的資源,例如 mutex lock、semaphore、記憶體頁面。執行緒透過 IPC(如 pipe、shared memory、message queue)與其他 Process 協作時,也可能發生死結,但這類死結 kernel 無法偵測,原因如下:
Kernel 能偵測死結的前提,是它必須對每一次資源的 acquire/release 都有完整的記錄,才能建立「哪個執行緒在等哪個執行緒」的等待關係圖(Wait-for Graph)並找出循環。對於 mutex lock,每次 acquire() 都是系統呼叫,kernel 清楚知道「Thread A 在等 Thread B 持有的鎖」。
但 IPC 的情況不同:kernel 提供的是傳輸管道(pipe 的讀寫、shared memory 的映射),而不是管道上傳遞的應用程式協定語意。當 Process A 阻塞在 read(pipe) 等待 Process B 的回覆,同時 Process B 阻塞在 read(pipe) 等待 Process A 的回覆,kernel 只看到「兩個 Process 都在等 I/O」,它無法知道這兩個等待之間存在循環依賴,因為那個依賴是由應用程式自己定義的協定所產生的,不是 kernel 管理的資源物件。
因此,這類死結的偵測與預防責任落在應用程式層,需要由設計 IPC 協定的開發者自行保證不會產生循環等待。
資源的使用生命週期:Request → Use → Release
無論哪種資源類型,執行緒使用資源都必須嚴格遵守三個步驟的順序。每一步都有對應的系統呼叫(System Call)或同步操作:
三個步驟的含義:
- Request(請求):執行緒請求某個資源。若資源目前可用,立刻授予並進入 Use 狀態;若資源被其他執行緒持有,執行緒進入等待(Wait),直到資源被釋放後再重試。
- Use(使用):執行緒對資源進行操作。例如若資源是 mutex lock,執行緒正在執行其 Critical Section;若是檔案,執行緒正在讀寫內容。
- Release(釋放):執行緒完成使用,將資源歸還系統。
以下是不同資源類型對應的實際系統呼叫:
| 資源類型 | Request 操作 | Release 操作 |
|---|---|---|
| I/O Device | request() | release() |
| File | open() | close() |
| Memory | allocate() | free() |
| Semaphore | wait() | signal() |
| Mutex Lock | acquire() | release() |