在Rust中编写双重检查锁定的正确方法是什么?

时间:2017-08-14 19:16:00

标签: concurrency rust double-checked-locking

我找到了this article,但它看起来不对,因为NNNNNNNNNNNNNNNNYYYYYYYYYYYNNNNNYYYYYYYYYYYYYYYYNNNNNNNNNNNYYYYYYYYYYNNN 无法保证锁定下的Cell与锁定set()之间的同步。

get()会影响其他非原子写操作吗?

我尝试用Atomic_.store(true, Ordering::Release)编写它,它看起来接近Java风格,但它失败了。在这种情况下,我无法找到正确使用AtomicPtr的示例。

1 个答案:

答案 0 :(得分:12)

  

Atomic_.store(true, Ordering::Release)会影响其他非原子写操作吗?

实际上,Ordering存在的主要原因是对非原子读写强加一些排序保证:

  • 在同一个执行线程中,对于编译器和CPU,
  • 以便其他线程按照他们将看到更改的顺序得到保证。

<强>宽松

约束越少Ordering;唯一不能重新排序的操作是对相同原子值的操作:

atomic.set(4, Ordering::Relaxed);
other = 8;
println!("{}", atomic.get(Ordering::Relaxed));

保证打印4。如果另一个帖子读到atomic4,则无法保证other是否为8

<强>推出/获取

分别写下和阅读障碍:

  • 发布将与store操作一起使用,并保证执行先前的写入,
  • 获取将与load操作一起使用,并保证进一步读取的值至少与相应store之前写入的值一样新鲜。< / LI>

所以:

// thread 1
one = 1;
atomic.set(true, Ordering::Release);
two = 2;

// thread 2
while !atomic.get(Ordering::Acquire) {}

println!("{} {}", one, two);

保证one1,并且对two一无所知。

请注意,加载Relaxed的{​​{1}}商店或加载Acquire的{​​{1}}商店基本上没有意义。

请注意,Rust提供Release:对于商店,它的行为为Relaxed,对于加载,它的行为为AcqRel,因此您不必记住哪个是...我不建议但是,因为所提供的保证是如此不同。

<强> SeqCst

最具约束力的Release。保证一次性跨所有线程排序。

  

在Rust中编写双重检查锁定的正确方法是什么?

因此,双重检查锁定就是利用这些原子操作来避免在不必要时锁定。

想法是有3件:

  • 一个标志,最初为假,并且一旦执行了该动作,就为真,
  • 一个互斥锁,以保证初始化期间的排除,
  • 要初始化的值。

并使用它们:

  • 如果标志为真,则值已初始化,
  • 否则,请锁定互斥锁,
  • 如果标志仍为false:初始化并将标志设置为true,
  • 释放锁定,现在初始化值。

困难在于确保非原子读/写正确排序(并以正确的顺序显示)。从理论上讲,你需要完全围栏;在实践中遵循C11 / C ++ 11内存模型的习惯用法就足够了,因为编译器必须才能使它工作。

让我们先检查一下代码(简化):

Acquire

有3个原子操作,通过注释编号。我们现在可以检查内存排序的哪种保证,每个必须提供正确性。

(1)如果为true,则返回对该值的引用,该引用必须引用有效内存。这要求在原子变为真之前执行对该存储器的写入,并且只有在该存储器为真之后才执行该存储器的读取。因此(1)需要Ordering和(3)需要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() } }

另一方面,

(2)没有这样的约束,因为锁定Acquire相当于一个完整的内存屏障:所有写入都保证在之前发生,所有读取只发生在之后。因此,此负载无需进一步保证,因此Release是最优化的。

因此,就我而言,这种双重检查的实施在实践中看起来是正确的。

为了进一步阅读,我真的推荐the article by Preshing,它链接在您链接的文章中。它显着突出了理论(围栏)和实践(原子载荷/商店被降低到围栏)之间的差异。