如果兩條 Thread 同時操作同一個 counter,這些步驟可能彼此交錯,最後造成 Race Condition。
而上一篇也留下了一個問題,如果我們需要保護的不是一個簡單的 counter++,而是一整段程式呢?
當一條 Thread 正在處理這組共享狀態時,其他會操作同一組狀態的 Thread 不要跑進來干擾。
於是我們需要一種 Synchronization Mechanism:Mutex
Mutex 真正控制的是:
哪一個 Thread 有權進入我們規定要由這把 Mutex 保護的 Critical Section。
也就是:
Thread A
│
│ lock(mutex)
▼
┌────────────────────┐
│ Critical Section │
│ │
│ balance -= 100 │
│ │
└────────────────────┘
│
│ unlock(mutex)
▼
(2)所有會存取這份 Shared State 的相關 Threads,都必須遵守同一套 locking protocol。
如果有人不遵守:Thread A → lock → 修改 balance → unlock 或 Thread B → 直接修改 balance
Race Condition 還是可能發生。
Mutex 不是把某個變數物理上鎖住,而是利用互斥規則協調多個 Thread 對 Shared State 的存取。
「拿不到鎖就等待。」
假設:
Thread A → 持有 Mutex
Thread B → 想取得 Mutex
CPU 一直執行這個 Loop。
稱為:Busy Waiting
也就是 Thread 雖然什麼實際工作都沒完成,卻還一直佔著 CPU 檢查條件。
這類思路和後面會談的 Spinlock 很有關係。
假設:int counter = 0;
兩條 Thread 都執行:counter++;
沒有 Mutex 時:
有了 Mutex 不代表:Concurrency Bug全部消失
Mutex 本身也有很多使用規則。
第一個最基本的就是:一定要 Unlock
當 error 發生時:
Thread A
取得 Mutex
↓
return
↓
沒有 unlock
其他 Thread 之後:
Thread B → lock → 等
Thread C → lock → 等
Thread D → lock → 等
這把 Mutex 可能一直沒被釋放。
(2)Critical Section 也不能亂包超大
看到 Mutex 可以解 Race Condition,有人可能乾脆:
lock();
整個程式全部放進來
unlock();
這樣雖然某些 Race Condition 可能消失,但也把 Concurrency 一起殺掉了。
Thread A
████████████████████████
整段都持有 Mutex
Thread B
等待........................
原本建立 Multi-thread 是希望不同工作可以有更多 concurrency。
結果因為 Lock 範圍太大:
A 跑完
↓
B 才跑
↓
C 才跑
幾乎變回 Serial Execution。
最後這點比較深,但如果真的要理解 Mutex,不能完全不講。
直覺會認為:
A 已經寫完了,所以 B 當然會看到 A 的結果。
現代電腦並不是單純:
CPU
↓
RAM
中間還有:
CPU Core
│
├── Registers
├── Store / Load 相關硬體機制
├── Cache
│
└── Memory
Mutex 的核心不是:「把一個變數鎖起來。」
而是:
讓競爭同一份 Shared State 的 Threads 遵守 Mutual Exclusion,確保同一時間只有適當的持有者能執行受保護的 Critical Section。
而 Mutex 自己之所以能可靠地「搶鎖」,底層又需要:
Atomic Primitive
↓
Lock Implementation
↓
Mutex
↓
保護更大的 Critical Section
下一篇: