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

Concurrency 在Rust中写入双重检查锁定的正确方法是什么?,concurrency,rust,double-checked-locking,Concurrency,Rust,Double Checked Locking,我找到了,但它看起来是错误的,因为单元格不能保证锁下的set()和锁上的get()之间的同步 原子存储(true,Ordering::Release)是否会影响其他非原子写操作 我试着用AtomicPtr编写它,它看起来很像Java风格,但失败了。在这种情况下,我找不到正确使用AtomicPtr的例子 原子存储(true,Ordering::Release)是否会影响其他非原子写操作 对 实际上,存在的主要原因是对非原子读写施加一些排序保证: 在同一执行线程中,对于编译器和CPU 这样其他线程

我找到了,但它看起来是错误的,因为
单元格
不能保证锁下的
set()
和锁上的
get()
之间的同步

原子存储(true,Ordering::Release)
是否会影响其他非原子写操作

我试着用
AtomicPtr
编写它,它看起来很像Java风格,但失败了。在这种情况下,我找不到正确使用
AtomicPtr
的例子

原子存储(true,Ordering::Release)
是否会影响其他非原子写操作

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

  • 在同一执行线程中,对于编译器和CPU
  • 这样其他线程就可以按照看到更改的顺序获得保证
放松

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

atomic.set(4, Ordering::Relaxed);
other = 8;
println!("{}", atomic.get(Ordering::Relaxed));
保证打印
4
。如果另一个线程读取到
atomic
4
,则无法保证
other
是否是
8

发布/获取

写入和读取障碍分别为:

  • Release用于
    store
    操作,并保证执行之前的写入操作
  • Acquire用于
    load
    操作,并保证进一步读取的值至少与相应
    存储之前写入的值一样新鲜
因此:

保证
one
1
,并且不涉及
two

请注意,带有
Acquire
加载的
released
存储或带有
released
加载的
Release
存储基本上没有意义

请注意,Rust提供了
AcqRel
:它的行为类似于存储的
Release
,以及装载的
Acquire
,因此您不必记住哪个是哪个。。。不过,我不建议这样做,因为提供的担保是如此不同

SeqCst

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


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

所以,双重检查锁定就是利用这些原子操作来避免不必要的锁定

这个想法是有3件:

  • 一个标志,最初为false,执行操作后为true
  • 互斥锁,以确保在初始化期间排除
  • 要初始化的值
并将其用作:

  • 如果标志为true,则表示值已初始化
  • 否则,锁定互斥锁
  • 如果标志仍然为false:初始化并将标志设置为true
  • 释放锁,值现在已初始化
难点在于确保非原子读/写的顺序正确(并且以正确的顺序可见)。从理论上讲,你需要完全的防护;实际上,遵循C11/C++11内存模型的习惯用法就足够了,因为编译器必须让它工作

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

struct-Lazy{
初始化:AtomicBool,
锁:互斥锁,
值:未完成,
}
暗示懒惰{
发布fn获取或创建
哪里
F:FnOnce()->T
{
if!self.initialized.load(Ordering::Acquire){/(1)
让_lock=self.lock.lock().unwrap();
if!self.initialized.load(Ordering::released){//(2)
让value=unsafe{&mut*self.value.get()};
*值=一些(f(值));
self.initialized.store(true,Ordering::Release);//(3)
}
}
不安全的{&*self.value.get()}.as_ref().unwrap()
}
}
有3个原子操作,通过注释编号。我们现在可以检查每种内存顺序都必须提供哪种类型的正确性保证

(1) 如果为true,则返回对该值的引用,该值必须引用有效内存。这要求在原子变为真之前执行对该内存的写入,并且只有在原子变为真之后才执行对该内存的读取。因此,(1)需要
获取
,(3)需要
释放

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

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



为了进一步阅读,我真的推荐你链接的文章中的链接。它突出了理论(围栏)和实践(原子负载/存储降低到围栏)之间的区别。

你的问题是合理的,但听起来像。您可能希望调查组件,如和演示的和。非常感谢您。但我看到一个错误(可能是我错了)。上次读取*self.value.get()参见self.initialized.load(Ordering::Acquire)==true时的写入操作。因为你提到的原因。但如果initialized==false,则在释放的存储和非原子read*self.value.get()之间没有获取的负载。例如,在Java中,这就是导致值不稳定的原因(SeqCst)。@АааааМааааМааааааМа。请注意,只有当
self.initialized
为true时,才使用
Acquire
语义加载:首先加载,然后知道它是否为tru
// thread 1
one = 1;
atomic.set(true, Ordering::Release);
two = 2;

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

println!("{} {}", one, two);
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()
    }
}