我发现this article,但它看起来不对,因为Cell
不保证在锁下的set()
和锁上的get()
之间的同步。在Rust中写入双重检查锁定的正确方法是什么?
Atomic_.store(true, Ordering::Release)
是否影响其他非原子写入操作?
我试图用AtomicPtr
写它看起来接近Java风格,但它失败了。在这种情况下,我找不到正确使用AtomicPtr
的示例。
我发现this article,但它看起来不对,因为Cell
不保证在锁下的set()
和锁上的get()
之间的同步。在Rust中写入双重检查锁定的正确方法是什么?
Atomic_.store(true, Ordering::Release)
是否影响其他非原子写入操作?
我试图用AtomicPtr
写它看起来接近Java风格,但它失败了。在这种情况下,我找不到正确使用AtomicPtr
的示例。
Atomic_.store(true, Ordering::Release)
是否影响其他非原子写操作?
是。
其实,主要原因Ordering
存在是强加给非原子读一些排序保证和写道:
宽松
越少约束Ordering
;不能被重新排序的操作只有在相同的原子值操作:
atomic.set(4, Ordering::Relaxed);
other = 8;
println!("{}", atomic.get(Ordering::Relaxed));
保证打印4
。如果另一个线程读取atomic
为4
,则不能保证other
是否为8
。
推出/获取
写入和读出的障碍,分别为:
store
操作中使用,并保证事先写入被执行时,load
操作一起使用,并保证进一步的读取将会看到至少一个值如同在相应的store
之前写的那样。所以:
// thread 1
one = 1;
atomic.set(true, Ordering::Release);
two = 2;
// thread 2
while !atomic.get(Ordering::Acquire) {}
println!("{} {}", one, two);
保证one
是1
,并且一无所知two
说。
。注意,Relaxed
商店与Acquire
负荷或Release
存储与Relaxed
负载本质上是无意义的。
注意防锈提供AcqRel
:它表现为Release
商店和Acquire
的负荷,所以你不必记住哪个是哪个?我不建议这样做虽然,因为提供的担保是如此不同。
SeqCst
最受限制的Ordering
。保证一次跨所有线程进行排序。
什么是写双重检查锁定在鲁斯特的正确方法?
因此,双重检查锁定是利用这些原子操作来避免不必要的锁定。
的想法是有3个:
,并以此作为这样的:
难点在于确保非原子读取/写入正确排序(并以正确的顺序变为可见)。从理论上讲,你需要完整的围栏;在实践中,遵循C11/C++ 11内存模型的习语将是足够的,因为编译器必须使其工作。
让我们来看看代码第一(简体):
struct Lazy<T> {
initialized: AtomicBool,
lock: Mutex<()>,
value: UnsafeCell<Option<T>>,
}
impl<T> Lazy<T> {
pub fn get_or_create<'a, F>(&'a self, f: F) -> &'a T
where
F: FnOnce() -> T
{
if !self.initialized.load(Ordering::Acquire) { // (1)
let _lock = self.lock.lock().unwrap();
if !self.initialized.load(Ordering::Relaxed) { // (2)
let value = unsafe { &mut *self.value.get() };
*value = Some(f(value));
self.initialized.store(true, Ordering::Release); // (3)
}
}
unsafe { &*self.value.get() }.as_ref().unwrap()
}
}
有3个原子操作,通过注释编号。我们现在可以检查哪种内存排序保证每个都必须提供正确性。
(1)如果为true,则返回值的引用,该引用必须引用有效内存。这要求在原子转换为真之前执行对该存储器的写入操作,并且只有在该真值为真后才能执行该存储器的读取操作。因此(1)要求Acquire
和(3)要求Release
。 (2)另一方面没有这样的约束,因为锁定一个Mutex
相当于一个完全的内存障碍:所有的写入都保证在之前发生,所有的读取只会在之后发生。因此,此负载不需要进一步的保证,所以Relaxed
是最优化的。
因此,就我而言,这种双重检查的实现在实践中看起来是正确的。
对于进一步阅读,我真的推荐the article by Preshing这是链接在你链接的一块。它显着地突出了理论(围墙)和实践(被降低到围墙的原子装载/商店)之间的差异。
非常感谢您。但我看到一个错误(可能是我错了)。最后一次读取* self.value.get()在self.initialized.load(Ordering :: Acquire)== true的情况下查看写入操作。出于你提到的原因。但是,如果初始化== false,则在已发布的商店和非原子读取* self.value.get()之间没有获得的负载。例如,在Java中,这是导致值变化的原因(SeqCst)。 –
@АлександрМеньшиков:我不认为这是一个错误。请注意,只有当'self.initialized'为true时,才会加载'Acquire'语义:首先加载并知道它是true还是false。因此,即使它最终评估为“false”,也可以使用“Acquire”语义来执行它,这些语义会在评估它之后对读取进行排序(并在存储之前用'Release'语义进行排序)。 –
是的,但是在具有'Release'语义的商店之后,我们不会在同一个线程**中使用'Acquire' **加载,并且只是在比赛中读取返回的值。并且这个读取没有看到写入,因为在任何写入之前调用了“Acquire”语义加载。可能是Rust对序列的保证,所有的读写操作都在共享内存的同一线程中 - 那么你是对的。只是Java不。这是我怀疑的原因。 –
你的问题是一个合理的问题,但它听起来像[X-Y问题](https://meta.stackexchange.com/questions/66377/what-is-the-xy-problem)。您可能希望调查像['Once'](https://doc.rust-lang.org/std/sync/struct.Once.html)和[lazy-static](https://crates.io/)这样的组件板条箱/懒惰静态)演示[这里](https://stackoverflow.com/q/27791532/155423)和[这里](https://stackoverflow.com/q/27221504/155423)。 – Shepmaster