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. 上面描述的是朴素的做法,实际使用时要做各种优化

 

你可能感兴趣的:(Hazard Pointer 改进设计——hazard version)