【发布时间】:2019-05-04 09:35:24
【问题描述】:
我对 Rust 还很陌生。 4 年前,我获得了计算机工程学位,我记得在我的操作系统课程中讨论(和理解)原子操作。然而,自从毕业以来,我一直主要使用高级语言工作,我不必关心像原子这样的低级内容。现在我开始接触 Rust,我很难记住这些东西是如何工作的。
我目前正在尝试了解hibitset 库的源代码,特别是atomic.rs。
这个模块指定了一个 AtomicBitSet 类型,它对应于 lib.rs 中的 BitSet 类型,但使用原子值和操作。据我了解,“原子操作”是保证不会被另一个线程中断的操作;相同值上的任何“加载”或“存储”都必须等待操作完成才能继续。根据该定义,“原子值”是其操作完全是原子的值。 AtomicBitSet 使用 AtomicUsize,这是一个 usize 包装器,其中所有方法都是完全原子的。但是,AtomicBitSet 指定了几个似乎不是原子的操作(add 和 remove),并且有一个原子操作:add_atomic。看看add 和add_atomic,我真的不知道有什么区别。
这里是add(逐字):
/// Adds `id` to the `BitSet`. Returns `true` if the value was
/// already in the set.
#[inline]
pub fn add(&mut self, id: Index) -> bool {
use std::sync::atomic::Ordering::Relaxed;
let (_, p1, p2) = offsets(id);
if self.layer1[p1].add(id) {
return true;
}
self.layer2[p2].store(self.layer2[p2].load(Relaxed) | id.mask(SHIFT2), Relaxed);
self.layer3
.store(self.layer3.load(Relaxed) | id.mask(SHIFT3), Relaxed);
false
}
此方法直接调用load() 和store()。我假设它使用Ordering::Relaxed 的事实使得这个方法不是原子的,因为另一个线程对不同的索引做同样的事情可能会破坏这个操作。
这里是add_atomic(逐字):
/// Adds `id` to the `AtomicBitSet`. Returns `true` if the value was
/// already in the set.
///
/// Because we cannot safely extend an AtomicBitSet without unique ownership
/// this will panic if the Index is out of range.
#[inline]
pub fn add_atomic(&self, id: Index) -> bool {
let (_, p1, p2) = offsets(id);
// While it is tempting to check of the bit was set and exit here if it
// was, this can result in a data race. If this thread and another
// thread both set the same bit it is possible for the second thread
// to exit before l3 was set. Resulting in the iterator to be in an
// incorrect state. The window is small, but it exists.
let set = self.layer1[p1].add(id);
self.layer2[p2].fetch_or(id.mask(SHIFT2), Ordering::Relaxed);
self.layer3.fetch_or(id.mask(SHIFT3), Ordering::Relaxed);
set
}
此方法使用fetch_or 而不是直接调用load 和store,我假设这就是使此方法具有原子性的原因。
但是为什么Ordering::Relaxed 的使用仍然允许它被认为是原子的?我意识到单个“或”操作是原子的,但完整的方法可以与另一个线程同时运行。不会有影响吗?
此外,为什么像这样的类型会公开非原子方法?仅仅是为了表现吗?这对我来说似乎很混乱。如果我要选择AtomicBitSet 而不是BitSet,因为它将被多个线程使用,我可能只想对其使用原子操作。如果我不这样做,我就不会使用它。对吧?
我也希望对 add_atomic 中的评论进行解释。按原样对我来说没有意义。非原子版本还不需要关心吗?这两种方法似乎在有效地做同一件事,只是原子性不同。
我真的很想在原子方面得到一些帮助。我认为我在阅读this 和this 后理解了排序,但两者仍在使用我不理解的概念。当他们谈论一个线程“看到”另一个线程时,这到底是什么意思?当说顺序一致的操作“跨所有线程”具有相同的顺序时,这意味着什么?处理器对不同线程改变指令顺序的方式不同吗?
【问题讨论】: