Epoch Reclamation:并发程序为什么要等“这一代线程”都走完再释放内存?
TL;DR
基于纪元的回收把退休对象标记到某一代,等所有可能看见它的参与者跨过该纪元后再批量释放,适合高并发无锁结构。
基于纪元的回收把退休对象标记到某一代,等所有可能看见它的参与者跨过该纪元后再批量释放,适合高并发无锁结构。

在并发程序里,直接 free 一个“看起来已经不用”的对象,往往是不安全的。另一个线程可能刚好在读取它,只是还没来得及留下明显的痕迹。Epoch-based Memory Reclamation(基于纪元的内存回收)把这个问题转换成了一个更容易管理的集体进度:只要所有可能看见旧对象的参与者都跨过了某个纪元,就可以回收这一代对象。
可以把线程想成进入博物馆的参观者,把 epoch 想成展厅换展的编号。对象在旧展厅里被撤下后,不会立刻扔掉,而是贴上当前编号。正在访问旧展厅的线程会“固定”在一个纪元;只要还有人停留,旧展品就保留。当所有参与者都离开这个纪元,回收器就知道这一批对象不会再被新的访问看到,可以批量清理。
典型实现会让线程进入临界区时 pin 自己,退出时 unpin。删除者把对象放入退休列表,随后观察全局或参与者的 epoch。只要退休对象的纪元比所有仍被固定的参与者都足够老,它就符合回收条件。和逐个扫描危险指针相比,这种方法更适合一次退休很多对象的场景,也能把回收工作批量化。
它的难点是“停滞线程”。如果某个线程进入临界区后长时间不退出,旧对象可能一直不能释放,内存占用会逐渐升高。因此实际系统要监控 pinned 时间,区分慢线程、崩溃线程和真正仍在使用对象的线程。不同库还会在全局 epoch、局部 epoch、线程本地缓存之间做不同取舍。
基于纪元的回收并不是传统垃圾回收:它通常没有对象可达性分析,也不会自动追踪所有引用,而是依赖并发数据结构遵守一套访问协议。它的价值在于把“每个对象有没有被某个线程读到”简化为“最老的活跃访问处在哪一代”。当读路径很短、参与者数量可控、删除可以延迟时,这种思路尤其漂亮。



