目錄
- Overview
- Lock和Latch辨析
- 設計目標
- 大致分類
- Hash Table Latches
- Page Latches
- Slot Latches
- B+Tree Latches
- 併發問題
- Latch Crabbing/Couping
- Optimistic Coupling(樂觀鎖)
- Leaf Node Scan
Overview
Lock和Latch辨析
Lock
:抽象的,邏輯的,整體統籌Latch
:具體的,原語性的,自我管理
本節主要探討Latch
。
設計目標
- 記憶體佔用少,無競態時執行迅速
- 等待時間過長時取消排程
大致分類
- 自旋鎖(Test-and-Set Spin Latch)
- 阻塞互斥鎖(Blocking OS Mutex)
- 讀寫鎖(Reader-Writer Latches)
特性 | Test-and-Set Spinlock | Blocking OS Mutex | Reader-Writer Locks |
---|---|---|---|
實現 | 基於原子操作的自旋等待 | 作業系統級阻塞 | 允許多讀單寫 |
鎖爭用時的處理 | 自旋等待,消耗 CPU | 阻塞等待,減少 CPU 消耗 | 讀操作可以併發,寫操作排他 |
適用場景 | 短期鎖、輕度鎖爭用 | 長期鎖、重度鎖爭用 | 讀多寫少 |
優點 | 無上下文切換,效能高 | 避免自旋消耗,適合長時間等待 | 讀寫併發,適合讀多寫少 |
缺點 | CPU 資源消耗高,鎖持有時間長時效率低 | 上下文切換開銷較高 | 寫者飢餓問題 |
C++中的mutex -> pthread -> Linux futex(fast user mutex):先在使用者空間用自旋鎖,如果獲取不到鎖,陷入核心態呼叫阻塞鎖進入阻塞佇列。
Hash Table Latches
兩種粒度:Page Latches和Slot Latches
Page Latches
- T1給page1上讀鎖,T2等待(如左上圖)
- T1檢視page2無讀鎖,給page2上讀鎖,釋放page1讀鎖;T2訪問page1,上寫鎖(如右上圖)
- T2訪問page2,但由於有T1讀鎖,等待(如左下圖)
- T1釋放page2讀鎖,T2結束等待,給page2上寫鎖,寫入
E|val
(如右下圖)
Slot Latches
整體過程和Page Latches類似,只不過粒度變了。
- T1給Slot A上讀鎖,T2給Slot C上寫鎖
- T1訪問Slot C,但是由於有T2的寫鎖,釋放Slot A寫鎖,在C等待(如左上圖)
- T2訪問Slot D,釋放Slot C寫鎖,給Slot D上寫鎖;T1可以訪問Slot C,上讀鎖(如右上圖)
- 重複上述兩個步驟(左下圖和右下圖)
B+Tree Latches
併發問題
相比於雜湊表,B+樹併發的難點在於樹的結構會發生分裂或合併。
- T1找到了需要刪除的值44(如左上圖)
- 刪除了值44,此時需要偷(steal)左兄弟的值41進行合併,保證葉子結點半滿,但是T1被排程,進入休眠(如右上圖)
- T2找到了需要刪除的值41,準備讀取值41,但是此時T2被排程,進入休眠(如左下圖)
- T1喚醒,進行結點合併,41移動到了新的位置
- T2被喚醒,讀取41,但是資料已經被移動(如右下圖)
Latch Crabbing/Couping
具體步驟:
- 得到父結點的鎖
- 得到子結點的鎖
- 如果子結點是安全的,釋放之前的鎖,否則不釋放
- 安全的定義:
- 對於查詢:不做要求
- 對於插入:不滿
- 對於刪除:多於半滿
例:查詢
例:刪除
例:插入
Optimistic Coupling(樂觀鎖)
觀察:在插入和刪除操作中,都會給根結點上寫鎖,造成系統在根結點處是序列的,有效能瓶頸。
實際上一個頁儲存一個結點,頁大小很大,大多數時候不需要結點分裂,刪除時結點也可以延遲合併,說明B+樹結構大多數時候不會變化,上寫鎖的代價太大。
基本思想:上讀鎖,發現衝突後重新上寫鎖。
步驟:
- 查詢:不變
- 插入/刪除:
- 和查詢一樣,在路徑上加讀鎖,到達葉子結點後加寫鎖
- 如果葉子結點不安全,重做;否則直接執行相關操作
Leaf Node Scan
葉子結點掃描順序:
- 垂直方向:自頂向底
- 水平方向:沒有限制
掃描方向衝突:
- 水平掃描方向不一致導致衝突
- 水平掃描和垂直掃描衝突
水平掃描方向不一致:讀鎖沒有衝突,互換讀鎖即可。
水平掃描方向不一致:帶寫鎖時會有衝突,選擇自我終結。
為什麼選擇自我終結:根本原因是latch是低階原語,不涉及全域性資訊,唯一知道的只有自己的資訊,所以選擇自我終結。
- 涉及到讀寫磁碟,等待時間不定
- 不知道其他程序進行到什麼程度,也不知道其他程序是什麼狀況
為什麼水平方向不能強制一個方向掃描:影響效率,在資料規模變大時更為明顯。
比如where子句是where id > 100000
,如果強制從左到右,得掃描100000條資料
水平掃描和垂直掃描方向不一致:
垂直到達葉子結點的操作,在遇到水平進行的操作時,同樣會遇到上述問題,處理方式也相同。