lock free数据结构内存回收技术-hazard pointer

时间:2022-05-02 16:14:36

lock free数据结构一般来说拥有比基于lock实现的数据结构更高的性能,但是其实现比基于lock的实现更为复杂,需要处理的难题包括预防ABA问题,内存如何重用和回收等。通常,最简单最有效的处理ABA问题的方法是在目标内存区域加入一个tag,每次目标内存区域被更新或者被重用时增加tag。线程最后一次读取目标内存区域后tag没有改变,CAS操作才能成功。比如对于无锁链表来说,目标内存区域就是链表节点。

但是,在目标内存区域中包含tag这种方法,当所有线程都不再需要使用某块内存区域时,没有机制可以检测。这也就导致这块内存区域不能被释放给系统供其他模块使用。hazard pointer可以用来解决这个问题。

hazard pointer的思想:每个线程可以有多个hazard pointer,和实现的lockfree数据结构相关,这些hazard pointer都会被记录到一个全局数组。线程私有的hazard pointer只能被自己修改,但是可以被其它所有线程读。比如5个线程,每个线程3个hazard pointer,那么全局数组有15个元素。一个线程操作无锁数据结构时,先将当前线程要访问的并且可能被别的线程销毁的目标内存区域的指针放在一个全局的地方,然后再检查一下这个指针是否有效,如果有效,后续就可以安全的读写目标内存区域。怎么判断有效?对于不同的数据结构来说,判断的方法不同,后续有例子。当一个线程想回收线程局部freelist中的内存区域时,首先会遍历全局hazard pointer数组,如果freelist中的目标内存区域的指针不在全局hazard pointer 数组中,说明所有线程都不会访问这个内存区域,那么这个线程就可以将这个目标内存区域释放给OS或者给其它模块使用。

以lock free stack为例:

lock free数据结构内存回收技术-hazard pointer

由于Push操作不会访问可能需要被free的节点的内容,所以不需要修改

重点关注Pop操作:

由于第4行会通过指针t来访问t指向的内存区域的内容,所以在第2行,就把t这个指针保护起来,放入这个线程所属的hazard pointer中(对于lock free stack来说,每个线程只需要一个hazard pointer),放入后,再通过第3行来判断t是否有效,如果t不再是栈顶,说明在第1行和第3行之间,别的线程已经成功pop了栈顶,重试。如果t仍然是栈顶,那么第4步用指针t来访问t指向的内存区域中的变量next是安全的,因为t在之前已经放入了hazard pointer中,别的线程在全局hazard pointer数组中能够找到这个hazard pointer,就不会释放这个节点。while 循环退出,所以pop成功,然后将自己的hazard pointer置NULL,表示本线程后续不会再访问这个节点。

参考资料:

Safe Memory Reclamation for Dynamic Lock-Free Objects Using Atomic Reads and Writes

High Performance Dynamic Lock-Free Hash Tables and List-Based Sets

http://en.wikipedia.org/wiki/ABA

Hazard Pointers: Safe Memory Reclamation for Lock-Free Objects