先澄清核心矛盾
无锁链表里,线程 A 可能刚把节点从链表摘下,线程 B 还在读取它。此时直接 drop 会造成悬垂引用。
两类主流方案
- Epoch Based Reclamation(EBR)
- 线程进入临界区时 pin 当前 epoch。
- 删除节点先放入 retired 列表。
- 只有当所有线程都跨过该 epoch,节点才可释放。
- Hazard Pointer(HP)
- 读取前先声明“我正在看这个指针”。
- 回收线程扫描所有 hazard slot。
- 未被保护的 retired 节点才能释放。
EBR 的工程特性
- 吞吐通常更高,读路径开销小。
- 线程暂停会拖慢全局回收进度。
- 更适合短临界区、线程活跃的服务。
HP 的工程特性
- 回收更精细,不容易被慢线程拖住。
- 读路径需要发布 hazard,CPU 开销更高。
- 更适合线程生命周期不可控的系统。
Rust 落地建议
// 伪代码:删除节点不立即释放,而是 retire
fn remove(node: Shared<Node>, guard: &Guard) {
if cas_unlink(node, guard) {
guard.defer_destroy(node); // 交给回收器延迟释放
}
}
- 使用经过验证的库(如 crossbeam)而不是手搓回收器。
- 把“最大 retired 数量”做成指标,防止隐性内存膨胀。
- 压测时加入
SIGSTOP慢线程场景,验证回收鲁棒性。
小结
锁不一定是瓶颈,错误回收一定是灾难。无锁结构上线前,先证明回收策略在“慢线程、抖动、长尾”下仍可控。