在Rust Atomics and Locks中,或多或少建议使用以下代码来正确实现简化的
drop
的Arc
特征:(代码是我的)
unsafe {
if 1 == (*self.inner).strong.fetch_sub(1, Release) {
fence(Acquire);
let box_ = Box::rom_raw(self.inner);
drop(box_);
}
}
其中
(*self.inner).strong
是 AtomicUsize
。
所以这是我的问题:
我知道
acquire
将与前一个 release
(与写入加载值的线程)建立“发生之前/同步”关系。为了正确地设置 drop
的值,我们需要将每个 Arc
的 fetch_sub
一个接一个地链接起来。
我不明白这里是如何保证这一点的。如果我们
acquired
原子的值,替换 1,然后 release
d,所有这些都在一个操作中(基本上是 fetch_sub
与 AcqRel
排序或比较和交换循环),我会理解。
这里,我们只是在加载1的时候“获取”。我对在
Release
上使用fetch_sub
的理解是,fetch
部分是relaxed
。基于这种理解,我对上述代码有两个问题:
这种情况不可能吗:
Thread A:
load: 2 (t = 1)
write 1 (t = ?)
Thread B:
load: 2 (t = 1)
write 1 (t = ?)
因此价值从未下降?我看不出
load
如何保证它对该值具有独占访问权限,这意味着它将始终按顺序加载。
我不是只在
1
的读取和下降之间建立了“发生之前”的关系吗?我认为这很好,因为如果我确实看到了 1
,我就可以 放弃,但是我如何保证看到 1
并且不会在原子上发生数据竞争?
我怀疑我的误解来自原子/排序提供的保证,但是
对于原子数据来说,原子操作始终是完全原子的(即
acquire-release
)。排序参数仅与对“其他”数据的操作相关。因此,release
仅意味着在操作之前发生的内存写入其他位置对于其他线程是可见的,类似地
acquire
意味着操作之后从其他位置读取内存将看到其他线程提交的所有内容操作时的线程。