我找到了this article,但它看起来错了,因为Cell不能保证锁下set()和锁上get()之间的同步.

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

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

推荐答案

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

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

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

Relaxed

不那么拘束的Ordering人;唯一不能重新排序的操作是对相同原子值的操作:

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

保证打印4张.如果另一个线程读取到atomic4,则无法保证other是否是8.

Release/Acquire

写入和读取障碍分别为:

  • Release将与store个操作一起使用,并保证执行之前的写入操作,
  • Acquire将与load个操作一起使用,并保证进一步读取的值至少与相应store之前写入的值一样 fresh .

所以:

// 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负载基本上没有意义.

请注意,Rust提供了AcqRel:它的作用相当于Release用于存储,Acquire用于装载,所以您不必记住哪个是哪个...不过,我不建议这样做,因为提供的担保是如此不同.

SeqCst

最受限制的Ordering个.保证一次在所有线程之间排序.


在Rust中写入双重判断锁定的正确方法是什么?

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

这个 idea 是有三件:

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

并使用它们:

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

难点在于确保非原子读/写的顺序正确(并且以正确的顺序可见).从理论上讲,你需要有完整的围栏;实际上,遵循C11/C++11内存模型的习惯用法就足够了,因为编译器must使其工作.

让我们先判断一下代码(简化):

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个链接在你链接的文章中.它突出了理论(围栏)和实践(原子负载/存储降低到围栏)之间的差异.

Rust相关问答推荐

如何定义使用拥有的字符串并返回拥有的Split的Rust函数?

为什么在Rust struct 中只允许最后一个字段具有动态大小的类型

即使参数和结果具有相同类型,fn的TypId也会不同

在actix—web中使用Redirect或NamedFile响应

包含嵌套 struct 的CSV

文档示例需要导入相关的 struct ,但仅在运行测试时.这是故意的行为吗?

无法实现整型类型的泛型FN

为什么&;mut buf[0..buf.len()]会触发一个可变/不可变的borrow 错误?

考虑到Rust不允许多个可变引用,类似PyTorch的自动区分如何在Rust中工作?

减少指示ProgressBar在Rust中的开销

`actix-web` 使用提供的 `tokio` 运行时有何用途?

为什么特征默认没有调整大小?

借来的价值生命周期 不够长,不确定为什么它仍然是借来的

为什么编译器看不到这个 `From` impl?

Rust: 目标成员属于哪个"目标家族"的列表是否存在?

Rust中如何实现一个与Sized相反的负特性(Unsized)

如何从 rust 中的同一父目录导入文件

在构建器模式中捕获 &str 时如何使用生命周期?

为什么指定生命周期让我返回一个引用?

如何在 Rust 中编写修改 struct 的函数