You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Folly的hazard pointer实现是否存在内存屏障使用错误问题?

问题

当使用Linux上的非对称内存屏障时,Folly的hazard pointer实现可以简化为如下逻辑:

Atomic<T*> source_ptr;

1. writer/updater: (retire operation)
    old_ptr = source_ptr.load();
    source_ptr.exchange(....);
    compiler barrier;====================
    move old_ptr to retirement list

2. consumer/reader: (try_protect operation)
    ptr = source_ptr.load();
    compiler barrier;====================
    if (source_ptr.load() == ptr) {
         success; start to use ptr;
    }
    fail; retry to get source_ptr;

3. reclaim:
   read all retirement list;
   heavy memory barrier --> membarrier() call; ===============
   read all hazard pointers;
   reclaim (delete) ptr if ptr is in retirement list but not in hazard pointers.

步骤3中的membarrier()可以分别与上述步骤1、步骤2同步,但步骤1和步骤2本身之间不存在同步机制。

因此我想知道下述执行时序是否可能发生,这属于实现bug还是我遗漏了相关要点?

source ptr: 可被hazard pointer保护的内存指针
thread 1: 尝试删除source ptr的线程
thread 2: 尝试读取source ptr的消费者线程
thread 3: 执行内存回收的线程(从退休列表中释放内存)

初始状态:
source ptr value = PTR_A

时间点0:
线程1(更新者):
将source ptr的值从PTR_A改为PTR_B,并将PTR_A放入退休列表(退休操作);
假设这些操作的结果对消费者线程(线程2)不可见,但对回收线程(线程3)可见,因为这里只有轻量级屏障。

时间点1:
线程3(回收线程):
读取退休列表,发现PTR_A在列表中。
调用重量级屏障`membarrier()`。
假设它先给线程2发送IPI,线程2所在的CPU处理完成。

时间点2:
线程2(消费者线程):
从source ptr读取到旧值PTR_A(新值还不可见)。
调用`try_protect`保护这个指针。
但此时这个操作对其他CPU还不可见,还是因为现在只有编译器屏障。线程2开始使用PTR_A。

时间点3:
线程3在时间点1发起的重量级屏障现在到达线程1并执行完成。
现在线程3开始收集hazard pointer列表。
但线程2在时间点2设置的hazard pointer对线程3还不可见。
所以线程3看不到这个保护记录,会判定PTR_A在退休列表中但不在hazard pointer列表中,也就是说它会开始删除PTR_A。

请问这是实现bug还是我遗漏了什么机制?


回答

你假设的执行时序不可能发生,不属于Folly hazard pointer实现的bug,是你对membarrier()的语义和hazard pointer的核心实现细节存在误解,核心原因有三点:

  • 你给出的try_protect简化逻辑遗漏了核心操作:两次source_ptr.load()结果相等判定成功后,必须先将拿到的ptr写入当前线程专属的hazard pointer原子槽位,之后才能开始使用指针。你描述的时序里跳过了这一步,导致对可见性的判断出现偏差。
  • Linux membarrier()系统调用的语义不支持你假设的执行流程:Folly使用的MEMBARRIER_CMD_PRIVATE_EXPEDITED模式下,membarrier()调用后会阻塞,直到所有当前运行该进程线程的CPU都处理完同步IPI、执行了一次全内存屏障之后才会返回。不存在你说的「先处理完线程2的IPI、再处理线程1的IPI,中间还能插入线程2的try_protect逻辑」的情况,线程3只有等所有CPU的屏障都执行完成后,才会继续执行读取hazard pointer列表的逻辑。
  • 内存屏障的可见性保证完全杜绝了悬空访问的可能:
    • 如果线程2的try_protect逻辑是在membarrier()下发的IPI处理之前执行的,那么IPI带来的全内存屏障会保证线程2对hazard pointer槽位的写入,在线程3后续读取hazard pointer列表时可见,PTR_A会被判定为正在被使用,不会被回收。
    • 如果线程2的try_protect逻辑是在IPI处理之后执行的,那么此时线程1对source_ptr的修改已经通过屏障同步到所有CPU,线程2两次load拿到的只会是新的PTR_B,根本不会拿到已经退休的PTR_A,也就不会出现访问已回收内存的情况。

内容的提问来源于stack exchange,提问作者sheeper

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.10.07 11:18:03