Rust中Arc的drop实现里Acquire-Release内存序的工作原理
关于简化版Arc实现中Drop逻辑的内存序疑问解答
首先明确核心前提:fetch_sub是原子操作,它的“读取当前值→减1→写入新值”是一个不可分割的整体,多个线程对同一个原子变量的原子操作会被串行化执行,不会出现中间步骤被打断的情况。
疑问1:是否会出现两个线程都读到2、写入1,导致值永远不被drop?
这种情况不可能发生。
原子操作的串行化特性决定了,线程A和线程B的fetch_sub必然是先后完整执行的:
- 假设初始
strong值为2,线程A先执行fetch_sub(1, Release):它会读取到2,写入1,返回值为2,不会进入drop分支。 - 接着线程B执行
fetch_sub(1, Release):此时它读取到的是1,写入0,返回值为1,触发后续的drop逻辑。
反过来,若线程B先执行,结果也是一样——总有一个线程会读取到1,进而触发drop,不会出现计数停留在1的情况。
疑问2:仅在读取到1时执行Acquire fence,如何保证同步、无数据竞争且能读到1?
无数据竞争的保证
所有对strong的操作(fetch_add用于clone,fetch_sub用于drop)都是原子操作,Rust的内存模型保证原子操作之间不存在数据竞争,每个操作都能正确读取到原子变量的当前值。
必然能读取到1的原因
strong的初始值对应Arc的持有数量:创建Arc时初始为1,每clone一次就fetch_add(1)增加计数,每drop一次就fetch_sub(1)减少计数。最终计数必然会从1减到0,此时执行fetch_sub的线程会读取到1(返回值为1),从而进入drop分支,不存在永远读不到1的可能。
同步关系的建立
- 当线程执行
fetch_sub(Release)时,Release语义保证:该线程中所有在fetch_sub之前对Arc内部数据的修改,都会被后续执行Acquire操作的线程看到。 - 当某个线程读取到返回值1(即它是最后一个持有Arc的线程),执行
fence(Acquire)会与所有之前执行fetch_sub(Release)的线程建立synchronize-with关系,进而保证:其他线程对Arc内部数据的所有修改,都happens-before当前线程的drop操作,确保drop时能看到完整的、正确的内部状态。
这种“Release fetch_sub + 条件性Acquire fence”是一种性能优化:只有最后一个线程需要承担Acquire屏障的开销,其他线程只需执行轻量的Release语义操作,避免了不必要的内存屏障开销。
内容的提问来源于stack exchange,提问作者Samuel Yvon
相关产品推荐
相关产品推荐

