主題
0.7 · 並行控制直覺
Part 0 前置知識
預估 20 分鐘
難度:入門
Race
Lock
Isolation
TL;DR · 本章重點
- Race Condition:兩個執行緒同時操作共享狀態,結果取決於誰先誰後 —— 銀行帳戶丟錢、計數器少算都是它。
- Lock / Mutex:確保臨界區一次只有一個執行緒進入。代價是競爭時等待、設計不當會 deadlock。
- 「原子操作」= 不可被打斷的操作:CPU 有 atomic instruction(compare-and-swap)、DB 有 atomic transaction —— 兩者層級不同但精神相同:要嘛全做、要嘛沒做。
- 隔離級別 = 兩個並發交易彼此能看見多少:Read Uncommitted / Read Committed / RR / SI / Serializable。
- Ch7 拆「SI 為何擋不住 write skew」、Ch9 問「跨節點如何達成共識」 —— 兩者的根都是並發。
如果你是前端開發者
你大概想:「JS 是 single-threaded、沒有共享記憶體 race condition,這章跟我有什麼關係?」
有關係——前端的 race 雖然不是 OS 執行緒的 race,但業務層 race 一樣會出現:
- 使用者按兩次送出:兩個
fetch(POST /orders)同時到後端 → 重複建單 - 樂觀更新撞到 server 回應:UI 已顯示「已按讚」、但 server 回 conflict → 該回滾還是接受?
- 多分頁同時操作:兩分頁同時讀
localStorage→ 各改各的 → 寫回時誰贏? - 管理後台兩個小編同時按「發佈」:各自基於「沒人在改」前提 → write skew
本章講的「鎖、原子性、隔離級別」,最終決定後端有沒有正確擋住你前端送出來的並發請求。看懂這章 = 知道哪些情境不能假設後端會擋、前端得自己做 idempotency token / 樂觀 UI 補償。
1) 銀行帳戶的經典例子
兩個 ATM 同時對同一帳戶 +100:
python
# Thread A # Thread B
balance = read() balance = read() # 兩邊都讀到 1000
balance += 100 balance += 100 # 兩邊都算成 1100
write(balance) write(balance) # 兩邊都寫 1100
# 預期 1200、實際 1100 → 丟了 100 元1
2
3
4
5
2
3
4
5
這就是 race condition。兩個操作的「讀-改-寫」交錯了。
2) Lock:把競爭序列化
python
# Thread A # Thread B
lock.acquire()
balance = read()
balance += 100
write(balance)
lock.release()
lock.acquire() # 等 A 釋放
balance = read()
balance += 100
write(balance)
lock.release()1
2
3
4
5
6
7
8
9
10
11
2
3
4
5
6
7
8
9
10
11
A 做完、B 才開始—— 結果 1200,正確。
Lock 的代價
- 競爭時等待:高並發下 lock contention 是效能殺手
- Deadlock:A 拿著 lock1 等 lock2、B 拿著 lock2 等 lock1—— 永遠卡住
- 保護不全:忘記 lock 一個地方就破功
3) Atomic Operation:硬體層的「不可分割」
CPU 提供 atomic instruction—— 一條指令完成「比較並交換」(CAS)或「加一」(fetch-and-add),中間不被打斷。
python
# 原子的「+= 1」—— 不需要 lock
atomic_counter.fetch_add(1)1
2
2
Java 的 AtomicInteger、Go 的 sync/atomic、C++ 的 std::atomic—— 都建立在 CPU atomic instruction(LOCK CMPXCHG、LOCK XADD 等)上。
Redis INCR 的原子性來源不一樣
Redis 的 INCR、INCRBY、LPUSH、MULTI/EXEC 不是靠 CPU atomic instruction,而是靠 single-threaded event loop——Redis 主執行緒一次只跑一條命令、根本不存在 race。這在語意層提供「執行緒安全」、但並非 ACID serializable transaction(MULTI/EXEC 中途錯誤不會 rollback、其他命令照樣繼續)。把 Redis 當「真正可序列化 KV」是常見誤解、Ch7 §7.3 會再回來談。
與 ACID 的 A 的關聯
ACID 的 atomicity 是「交易層級的不可分割」(多個 SQL 全做或全不做);CPU atomic instruction 是「單一指令層級的不可分割」。兩個層次、相同精神。
4) 隔離級別:DB 對並發交易的承諾
並發交易 T1、T2 同時跑:
sql
-- T1 -- T2
BEGIN; BEGIN;
SELECT balance SELECT balance
FROM accounts; FROM accounts;
UPDATE accounts UPDATE accounts
SET balance = balance SET balance = balance
- 100; - 50;
COMMIT; COMMIT;1
2
3
4
5
6
7
8
2
3
4
5
6
7
8
T1 與 T2 彼此能看見對方的中間狀態嗎?這由隔離級別決定:
| 級別 | 能擋住的異常 | 擋不住的 |
|---|---|---|
| Read Uncommitted | (什麼都擋不住) | 髒讀、髒寫… |
| Read Committed | 髒讀、髒寫 | 不可重複讀、phantom |
| Repeatable Read | + 不可重複讀 | phantom(依實作)、write skew |
| Snapshot Isolation | + phantom 純讀 | write skew(phantom 變種寫入) |
| Serializable | 全部 | (效能代價最高) |
SI「擋 phantom」要分純讀 vs 寫入
SI 用 MVCC + first-committer-wins 對「phantom 純讀」(同交易兩次同條件查 → 結果集變大)能擋住——讀的是 snapshot、後來別人插入不影響當前 snapshot。但 SI 擋不住 write skew——兩個交易各自讀 snapshot、各自做決策、各自寫不同列(如雙醫師同時請假、信箱重複註冊),DDIA Ch7 把這類定義為「phantom 的寫入變種」。要擋 write skew 必須升到 Serializable(PG SSI 用 SIREAD 偵測 rw-cycle、MySQL 用 next-key lock)。
DDIA Ch7 的核心訊息
很多人以為自己用了 "Serializable",其實只用到 RR 或 SI。 預設級別各家 DB 不同:
- PostgreSQL 預設 Read Committed
- MySQL InnoDB 預設 Repeatable Read(但 MySQL 的 RR ≠ SQL 標準的 RR)
- Oracle 預設 Read Committed
5) 跨節點的並行:CAP 與共識的伏筆
單機並行(threads 共享記憶體)已經很難。 跨節點並行(節點之間靠網路通訊)更難——
- 訊息可能丟、可能延遲、可能重複
- 節點可能掛、可能 GC pause 5 秒
- 沒有「全域時鐘」可信任
這就是 DDIA Ch9 共識整章在處理的事。Raft 與 Paxos 都是「在這種混亂環境下,讓 N 個節點對某個值達成一致」的演算法。
6) 與 DDIA 章節的對應
| DDIA 章節 | 用到的並行概念 |
|---|---|
| Ch7 交易 | Race condition、lock、隔離級別、SI、Serializability |
| Ch8 麻煩 | Process pause、fencing token、partial failure |
| Ch9 一致性與共識 | Linearizability vs serializability、Raft / Paxos、全序廣播 |
| Ch11 串流 | Exactly-once semantics、idempotent processing |
想更深入?
| 資源 | 內容 |
|---|---|
| Jepsen Consistency Models | 一張圖看懂所有 consistency 級別 |
| The Little Book of Semaphores | 並行同步問題經典,免費 |
| Hermitage tests | Kleppmann 自己做的隔離級別實驗,看各 DB 在哪些 anomaly 上會破功 |
| Raft 視覺化 | 共識演算法動畫,Ch9 必看 |
章末自評
章末測驗 · p0-concur
Q1. 應用 兩個 ATM 同時對同一帳戶 +100,銀行最終餘額少了 100。這個現象的名字是?
Q2. 應用 你用 PostgreSQL 的預設隔離級別(Read Committed),下列哪個異常 PG 已經幫你擋掉?
Q3. 應用 本章把「並發」分成單機(lock / atomic ops)與多機(隔離級別 / 共識)兩個層次。為什麼 Ch7(交易)與 Ch9(共識)都會建立在這章的基礎上?
The Next Chapter
Ch1 可靠、可擴展、可維護
Continue Reading→