rust - 关于 Rust 内存顺序的一些困惑
问题描述
我对 Rust 内存屏障有一些疑问,让我们看看这个例子,基于这个例子,我做了一些改变:
use std::cell::UnsafeCell;
use std::sync::atomic::{AtomicUsize, Ordering};
use std::sync::{Arc, Barrier};
use std::thread;
struct UsizePair {
atom: AtomicUsize,
norm: UnsafeCell<usize>,
}
// UnsafeCell is not thread-safe. So manually mark our UsizePair to be Sync.
// (Effectively telling the compiler "I'll take care of it!")
unsafe impl Sync for UsizePair {}
static NTHREADS: usize = 8;
static NITERS: usize = 1000000;
fn main() {
let upair = Arc::new(UsizePair::new(0));
// Barrier is a counter-like synchronization structure (not to be confused
// with a memory barrier). It blocks on a `wait` call until a fixed number
// of `wait` calls are made from various threads (like waiting for all
// players to get to the starting line before firing the starter pistol).
let barrier = Arc::new(Barrier::new(NTHREADS + 1));
let mut children = vec![];
for _ in 0..NTHREADS {
let upair = upair.clone();
let barrier = barrier.clone();
children.push(thread::spawn(move || {
barrier.wait();
let mut v = 0;
while v < NITERS - 1 {
// Read both members `atom` and `norm`, and check whether `atom`
// contains a newer value than `norm`. See `UsizePair` impl for
// details.
let (atom, norm) = upair.get();
if atom != norm {
// If `Acquire`-`Release` ordering is used in `get` and
// `set`, then this statement will never be reached.
println!("Reordered! {} != {}", atom, norm);
}
v = atom;
}
}));
}
barrier.wait();
for v in 1..NITERS {
// Update both members `atom` and `norm` to value `v`. See the impl for
// details.
upair.set(v);
}
for child in children {
let _ = child.join();
}
}
impl UsizePair {
pub fn new(v: usize) -> UsizePair {
UsizePair {
atom: AtomicUsize::new(v),
norm: UnsafeCell::new(v),
}
}
pub fn get(&self) -> (usize, usize) {
let atom = self.atom.load(Ordering::Acquire); //Ordering::Acquire
// If the above load operation is performed with `Acquire` ordering,
// then all writes before the corresponding `Release` store is
// guaranteed to be visible below.
let norm = unsafe { *self.norm.get() };
(atom, norm)
}
pub fn set(&self, v: usize) {
unsafe { *self.norm.get() = v };
// If the below store operation is performed with `Release` ordering,
// then the write to `norm` above is guaranteed to be visible to all
// threads that "loads `atom` with `Acquire` ordering and sees the same
// value that was stored below". However, no guarantees are provided as
// to when other readers will witness the below store, and consequently
// the above write. On the other hand, there is also no guarantee that
// these two values will be in sync for readers. Even if another thread
// sees the same value that was stored below, it may actually see a
// "later" value in `norm` than what was written above. That is, there
// is no restriction on visibility into the future.
self.atom.store(v, Ordering::Release); //Ordering::Release
}
}
基本上,我只是将判断条件改为and方法if atom != norm
中的内存顺序。get
set
根据我目前所了解的,所有的内存操作(1.不需要这些内存操作在同一个内存位置上操作,2.无论是原子操作还是普通内存操作)都发生在a之前store Release
,之后的内存操作将对内存操作可见load Acquire
。
我不明白为什么if atom != norm
并不总是正确的?实际上,从示例中的评论中,它确实指出:
但是,不能保证其他读者何时会看到下面的商店,从而看到上面的内容。另一方面,也不能保证这两个值对读者来说是同步的。即使另一个线程看到下面存储的相同值,它实际上也可能看到
norm
比上面写的值“晚”的值。也就是说,对未来的可见性没有限制。
有人可以向我解释为什么norm
可以看到一些“未来价值”吗?
同样在这个 c++ 示例中,导致这些语句出现在代码中的原因是否相同?
v0、v1、v2 可能会变成 -1、部分或全部。
解决方案
所有内存操作...发生在存储释放之前,将在加载获取后对内存操作可见。
仅当获取负载看到来自发布存储的值时,这才是正确的。
如果不是,则获取负载在发布存储全局可见之前运行,因此无法保证任何事情;你实际上并没有与那个作家同步。的加载norm
发生在获取加载之后,因此在该时间间隔内,另一个存储可能已成为全局可见的1 。
此外,norm
存储首先完成2因此即使atom
同时norm
加载 和 (例如,通过一个广泛的原子加载),它仍然有可能看到尚未norm
更新。atom
脚注 1:(或对这个线程可见,在罕见的机器上可能发生这种情况而不是全局可见的,例如 PowerPC)
脚注 2:唯一的实际保证是不迟到;它们都可以作为一个更广泛的事务变得全局可见,例如允许编译器将norm
存储和atom
存储合并到一个更广泛的原子存储中,或者硬件可以通过存储缓冲区中的存储合并来做到这一点。因此,您可能永远不会观察到norm
更新的时间间隔atom
;它取决于实现(硬件和编译器)。
(IDK Rust 在这里给出了什么样的保证,或者它如何正式定义同步和内存顺序。但是获取和释放同步的基础是相当普遍的 。https://preshing.com/20120913/acquire-and-release-semantics/。在 C++ 中,在没有实现同步的情况下读取非原子norm
将是数据竞争 UB(未定义的行为),但当然,当为真实硬件编译时,我描述的效果是在实践中会发生的情况,无论源语言是 C++ 还是 Rust .)
推荐阅读
- javascript - 未捕获的 SyntaxError:将参数添加到 jstl 中的 javascript 方法时出现无效或意外的标记
- html - 模态被搞砸了,现在溢出了
- ios - 在 Swift 中使用 Glimpse 时将文档目录指定为文件输出 URL
- objective-c - 如何在mac应用程序中获取屏幕分辨率的纵横比
- c# - C# 组合框控制整个表单
- javascript - 从按钮 OnClientClick js 函数启用 asp.net txtbox
- python-3.x - 无法在python中访问另一个类中的一个类的对象
- python - 如何区分相同的USB转串口转换器?
- android - 如何允许我的数据库中的用户仅从 s3 存储桶下载他的图像?
- elasticsearch - ElasticSearch:在时间戳字段上按小时和分钟过滤数据