Hazard Pointer 改進設計——hazard version

Hazard Version

1 lock free算法的內存回收問題

             hazard pointer算是比較通用也比較實用的內存回收機制,但是也有缺點,這裏先介紹hazard pointer的做法,然後介紹一種改進方案。


2 hazard pointer

按hazardpointer的做法,一個節點要區分以下兩種狀態

  1. retired:這個節點被刪掉。在retire之後開始的操作不可能訪問到這個節點,但正在進行中的操作可能還會訪問這個節點。
  2. reclaimed:這個節點的內存被回收,以後不能再訪問

一個節點在retire之後並不能立即reclaim,因爲正在進行中的操作可能還會訪問這個節點。內存回收的本質就是判斷retired的節點什麼時候可以reclaim。

hazard pointer這種方案的做法就是通過維護一個hazardpointer的集合來判斷一個節點是否還可能被訪問:

  1. 如果一個節點已經retire,並且這個節點的指針沒有出現在hazard pointer的集合中,那麼就表示不會有任何線程會在未來訪問它。
  2. 爲了保證性質1,任何一個線程在訪問一個節點之前,需要先把這個節點的指針加入到hazard pointer的集合。
  3. 雖然初看起來是對的,但實際上2並不能保證性質1,因爲一個線程隨時可能決定要訪問一個節點A,並把節點A的指針加入向hazard pointer集合。 如果在A的指針加入集合之前掃描hazard pointer集合,就會判斷說節點A可以回收。

考慮3中提到的問題:只要在判斷一個節點的指針是否出現在hazard pointer的集合中之前把這個節點的指針加入到hazard pointer集合就足夠了。也就是說一個節點的事件按下面的順序發生肯定是合法的:

  1. 被加入到hazard pointer集合
  2. 被retire
  3. 被reclaim

1和2的時序是需要保證,方法就是:在把一個節點加入到hazard pointer集合後如果發現這個節點已經retire了,就認爲這個節點的指針加入到hazard pointer集合失敗。


3 hazard version

hazard pointer的要求有兩個:

  1. 需要爲每個要訪問的節點準備一個hazard pointer,對有些數據結構,比如tree來說,hazard pointer的數目比較多
  2. 需要有一個方法檢測一個節點是否已經retire,對有些數據結構來說,這個判斷不好做。

一個變通的方法是爲每個retire的節點賦予一個retire version,每次操作之前要取一個全局version加入到hazard version集合。只要一個節點的retire version大於任意一個hazard version,那麼這個節點就有可能在未來被訪問。

3.1 具體方案

設置以下全局變量:

  1.    globalVersion: 類型是int64_t, 表示全局version
  2. : HazardVersionSet:是一個hazard version的集合, 是個multi set

線程A: 執行正常操作

// 操作之前: 獲取全局version,並加入到hazard version set

hazardVersion = atomic_read(&globalVersion)

hazardVersionSet.add(hazardVersion)

mem_barrier();

// ...... 做各種操作, 假設操作過程中retired node被放到retiredNodeList.

// 操作完成: 增加globalVersion, 並把globalVersion賦給每個retired node

retireVersion = atomic_add_and_fetch(&globalVersion)

for p in retiredNodeList:

   p.retiredVersion= retireVersion

// 操作完成,把hazardVersion移除

hazardVersionSet.remove(hazardVersion)

// 最後把retired node加入到waitToReclaimNodeList

waitToReclaimNodeList.append(retiredNodeList)

線程B: 執行reclaim

// 假定待回收的節點放在waitToReclaimNodeList

startReclaimVersion = atomic_read(&globalVersion)

minHazardVersion = hazardVersionSet.min()

reclaimableVersion = min(startReclaimVersion,minHazardVersion)

for p in waitToReclaimNodeList:

   ifp.retiredVersion  < reclaimableVersion

      reclaim(p)

3.3 注意的問題

  1. 爲了保證正確性,線程A必須要在 mem_barrier() 之後重新讀取數據結構的"根指針", 否則就保證不了不會讀取到在 mem_barrier() 之前就retire的節點。
  2. 上面描述的是樸素的做法,實際使用時要做各種優化

 

發佈了78 篇原創文章 · 獲贊 9 · 訪問量 23萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章