Race Condition 是什麼
多個 thread 同時讀寫同一塊記憶體,執行結果取決於 thread 的排程順序——這就是 race condition。
最經典的例子:兩個 thread 同時對 counter += 1
# counter = 100,兩個 thread 各加 1,預期 102,但可能得到 101
# Thread A 讀 counter(值:100)
# Thread B 讀 counter(值:100)← B 還沒讀到 A 的更新
# Thread A 寫 counter(101)
# Thread B 寫 counter(101)← B 用舊值 100+1,蓋掉 A 的更新
# 結果:101,少了 1這個 bug 的特性:
- 單 thread 測試看不到(只有一個 thread 的時候順序固定)
- 低並發下極少觸發(thread 的時機剛好重疊才會爆)
- 線上突然爆發(高流量下 thread 衝突頻率急劇上升)
銀行轉帳的 Race Condition
更嚴重的案例——A 轉 100 給 B,同時 C 轉 100 給 B:
def transfer(from_account, to_account, amount):
if from_account.balance >= amount: # ← 讀
from_account.balance -= amount # ← 寫
to_account.balance += amount # ← 寫
# A 帳戶:200,轉 150 給 B(Thread 1)
# C 帳戶:200,轉 150 給 B(Thread 2)
# Thread 1 讀 A.balance = 200,條件成立
# Thread 2 讀 A.balance = 200,條件成立(A 的扣款還沒寫回)
# Thread 1 寫 A.balance = 50
# Thread 2 寫 A.balance = 50 ← 蓋掉了,A 少扣了 100解法是把整個 check-then-act 包成 atomic 操作,或用 lock 保護:
import threading
lock = threading.Lock()
def transfer(from_account, to_account, amount):
with lock:
if from_account.balance >= amount:
from_account.balance -= amount
to_account.balance += amountDeadlock 是什麼
Deadlock 是「每個 thread 都在等別人釋放 lock,但沒有人能繼續」。
Thread A 持有 Lock 1,等待 Lock 2
Thread B 持有 Lock 2,等待 Lock 1
→ 永遠等下去
Coffman 條件(4 個都成立才會 deadlock):
| 條件 | 說明 |
|---|---|
| Mutual Exclusion | 資源只能被一個 thread 持有 |
| Hold and Wait | thread 持有資源的同時等待另一個 |
| No Preemption | 資源不能被強制剝奪,只能自願釋放 |
| Circular Wait | A 等 B,B 等 C,C 等 A(形成環) |
打破任何一個條件就能避免 deadlock。實務上最常用的是打破 Circular Wait:
# 壞的:Thread 1 拿 lock_a 再拿 lock_b,Thread 2 拿 lock_b 再拿 lock_a
# 好的:統一順序,所有人都先拿 lock_a 再拿 lock_b
# 或用 tryLock 加 timeout:拿不到就放棄,等一段時間再試
import threading
def transfer_safe(from_account, to_account, amount):
# 用 id 決定 lock 的獲取順序,保證全局一致
first = min(from_account.id, to_account.id)
second = max(from_account.id, to_account.id)
locks = {account.id: account.lock for account in [from_account, to_account]}
with locks[first]:
with locks[second]:
if from_account.balance >= amount:
from_account.balance -= amount
to_account.balance += amount怎麼 Debug Concurrent Bug
Race condition 的 debug 工具:
- Java:
-Djdk.internal.ref.UnsafeFieldUpdaterImpl+ ThreadSanitizer(-fsanitize=thread) - Go:內建 race detector,
go test -race或go run -race main.go - Python:
threading.Lock+ logging timestamp 手動追蹤(Python 有 GIL,純 Python code 不太會有 race condition,但 C extension 或 multiprocessing 才會)
Deadlock 的 debug 工具:
- JVM:
jstack <pid>輸出所有 thread 的 stack trace 和 lock 持有狀態,搜尋BLOCKED和deadlock - Go:程式自動 panic 並印出所有 goroutine stack(
all goroutines are asleep - deadlock!) - Linux:
strace -p <pid>看 system call 卡在哪
最有效的預防策略:
- 盡量用不可變資料(immutable),沒有 mutation 就沒有 race
- 用 message passing 代替共享記憶體(Go channel、Erlang actor)
- 需要 lock 時,文件化 lock 的獲取順序並全局一致執行
- DB 層用 transaction + optimistic/pessimistic locking,不要自己用 application lock 管 DB 狀態
和 C03 Concurrency Patterns 的分工
本篇是概念層:理解 race condition 和 deadlock 是什麼,以及如何識別和預防。
C03 的 concurrency patterns(Thread Pool、Read-Write Lock、Producer-Consumer、Active Object、Reactor)是解法層:在理解概念後,用 pattern 把 concurrent 程式碼組織成可維護的結構。
理解本篇之後,C03 的 pattern 才有意義——Thread Pool 為什麼存在、Read-Write Lock 解了什麼問題、Producer-Consumer 的 buffer 為什麼能緩解 race。