hazard pointer

hazard pointer

转载自: http://hi.baidu.com/rodimus/item/f6539cc179894f2f47d5c0ef

这是用于解决多线程并发下内存的回收,一块内存被释放之后,并不能直接交回给操作系统,因为可能有别的线程正在读这个数据。那什么时候交还给操作系统呢?不 同的应用不一样。等的时间太短,可能会读到已经释放的内存,会CORE; 时间太长,内存的利用率会下降。以前做检索系统的时候,有些很山寨的方法解决这些问题。


通用的解决方案有:

(1)加读写锁,应用的时候要注意锁的粒度,锁的粒度太大,并发度低,锁粒太细,需要很多锁,锁本身的资源开销很大。这种情况下一般假设读写是均匀分布在各个内存单元的,但是很多应用里读写是比较集中的。
(2)引用计数,给每个内存节点加一个计数,要用的时候加1,用完减1,减到0就表示不用了,这个的内存开销会比较大。
(3)就是要介绍的hazard pointer:

  观察到,读线程虽然需要读很多数据,但是在特定时刻,它需要读的内存单元是很少的,比如检索系统读倒排,其实它就只需要读链表上某个节点,不需要读整个链表,有点飞矢不动的感觉。所以别的线程删除数据的时候,只需要看一下所有线程正在读的内存,如果该内存没线程在读,就可以删除了。具体实现的时候,每个线程开一个大小为K的数组,用于存储它正在读的内存地址,每个线程也有一个链表记录它需要删除的内容。简单来说是这样,但是要解决的问题还是很多的:


 (a)扩展性,线程数N可能很大,线程在某一瞬间需要同时读的内存K可能很多,这样N*K的话,数量会很大,会使得遍历的性能很差。所以可以考虑用一个拉链把所有线程的hazard pointer串起来
 (b)性能,删除的时候需要遍历所有的hazard pointer,确认没有线程正在读这块内存,反复遍历会很耗时间。所以把需要删除的内存先缓存起来,再一次遍历hazard pointer,批量回收内存
 (c)多写,这个需要上层自己解决,利用CAS之类的原语重复检验,如果在获得原始数据,修改数据之间,有别的线程已经完成操作,则需要把工作重复做一遍
 (d)读写并发,考虑两种情况,一是读线程已经读完了,但是写线程还认为它在读,这种情况是没有问题的,只是降低了回收的速度:

  一是写线程认为没有线程在读,准备回收的时候,有线程在读。这需要上层自己解决,读线程,如果在获得数据、把数据加为hazard pointer之间,有删除操作发生,则需要重新读取。写线程,在把内存放进待删除拉链之前,修改相应指针,让后来的读线程读不到数据,让已经读到但是没加进hazard pointer的线程能获得通知。

从论文上看到的实测数据,这一方案比读方法(1)(2)都要优,但是论文的数据里并没有讨论hazard pointer的数量,应该是默认一个线程一个hazard pointer测的。

你可能感兴趣的:(poi)