如果有两个线程访问全局变量,那么许多教程都说使变量volatile变为阻止编译器将变量缓存在寄存器中,从而无法正确更新.但是,访问共享变量的两个线程是通过互斥锁来调用保护的东西不是吗?但是在这种情况下,在线程锁定和释放互斥锁之间,代码处于一个关键部分,只有那个线程可以访问变量,在这种情况下变量不需要是volatile?
那么多线程程序中volatile的用途/目的是什么?
好吧,我已经从SO关于x86处理器围栏阅读下列适量(LFENCE,SFENCE和MFENCE):
和:
而且我必须说实话,我还不能确定何时需要围栏.我试图从删除完全锁定并尝试通过栅栏使用更细粒度的锁定的角度来理解,以最小化延迟延迟.
首先,这是我不明白的两个具体问题:
有时在进行存储时,CPU会写入其存储缓冲区而不是L1缓存.但是,我不了解CPU执行此操作的条款?
CPU2可能希望加载已写入CPU1的存储缓冲区的值.据我了解,问题是CPU2无法在CPU1的存储缓冲区中看到新值.为什么MESI协议不能将刷新存储缓冲区作为其协议的一部分?
更一般地,可以请人试图描述的总体方案,并帮助时解释LFENCE/ MFENCE和SFENCE被需要的指令?
NB阅读本主题的一个问题是,当我只对Intel x86-64架构感兴趣时,"通常"为多CPU架构编写的文章数量.
Linux内核lock; addl $0,0(%%esp)用作写屏障,而RE2库xchgl (%0),%0用作写屏障.有什么区别,哪个更好?
x86还需要读屏障指令吗?RE2将其读屏障功能定义为x86上的无操作,而Linux lfence根据SSE2是否可用将其定义为无操作或无操作.什么时候lfence需要?
在英特尔优化手册似乎对存储缓冲区的数量存在于处理器的许多地方,但谈判没有谈存储缓冲区的大小.这是公共信息还是商店缓冲区的大小保留为微架构细节?
我正在研究的处理器主要是Broadwell和Skylake,但其他人的信息也不错.
另外,存储缓冲区究竟做了什么?
这个问题是对此的跟进/澄清:
MOV x86 指令是否实现了 C++11 memory_order_release 原子存储?
这表明MOV汇编指令足以在 x86 上执行获取-释放语义。我们不需要LOCK,围栏xchg等。但是,我很难理解这是如何工作的。
英特尔文档第 3A 卷第 8 章指出:
https://software.intel.com/sites/default/files/managed/7c/f1/253668-sdm-vol-3a.pdf
在单处理器(核心)系统中......
- 读取不会与其他读取重新排序。
- 写入不会与较旧的读取重新排序。
- 对内存的写入不会与其他写入重新排序,但以下情况除外:
但这是针对单核的。多核部分似乎没有提到如何强制执行负载:
在多处理器系统中,以下排序原则适用:
- 单个处理器使用与单处理器系统相同的排序原则。
- 所有处理器都以相同的顺序观察单个处理器的写入。
- 来自单个处理器的写入与来自其他处理器的写入无关。
- 记忆排序服从因果关系(记忆排序尊重传递可见性)。
- 除了执行存储的处理器之外的处理器以一致的顺序看到任何两个存储
- 锁定指令具有总顺序。
那么如何才能MOV单独促进获取释放呢?
为什么是std::atomic的 store:
std::atomic<int> my_atomic;
my_atomic.store(1, std::memory_order_seq_cst);
Run Code Online (Sandbox Code Playgroud)
在xchg请求具有顺序一致性的商店时执行?
从技术上讲,具有读/写内存屏障的普通商店不应该足够吗?相当于:
_ReadWriteBarrier(); // Or `asm volatile("" ::: "memory");` for gcc/clang
my_atomic.store(1, std::memory_order_acquire);
Run Code Online (Sandbox Code Playgroud)
我明确地谈论x86和x86_64.商店有隐含的获取围栏.
在阅读一致性模型(即 x86 TSO)时,作者通常会使用包含大量 CPU、相关存储缓冲区和私有缓存的模型。
如果我的理解是正确的,存储缓冲区可以被描述为队列,CPU 可以在其中放置他们想要提交到内存的任何存储指令。因此,顾名思义,它们是store缓冲区。
但是当我阅读这些论文时,他们倾向于谈论加载和存储的交互,诸如“稍后加载可以通过较早存储”之类的陈述有点令人困惑,因为它们几乎似乎在谈论存储缓冲区将同时具有加载和存储,当它没有时 - 对吗?
所以也必须有一个他们没有(至少明确地)谈论的负载存储。另外,这两者必须以某种方式同步,所以两者都知道什么时候可以从内存加载并提交到内存——或者我是否遗漏了什么?
任何人都可以对此有所了解吗?
编辑:
让我们看一下“内存一致性和缓存一致性入门”中的一段:
为了理解 TSO 中原子 RMW 的实现,我们将 RMW 视为紧跟在存储之后的负载。由于 TSO 的排序规则,RMW 的负载部分无法通过较早的负载。乍一看,RMW 的加载部分可能会传递写入缓冲区中较早的存储,但这是不合法的。如果 RMW 的加载部分通过较早的存储,则 RMW 的存储部分也必须通过较早的存储,因为 RMW 是原子对。但是由于在 TSO 中不允许 store 相互传递,因此 RMW 的负载部分也不能通过较早的 store
进一步来说,
由于 TSO 的排序规则,RMW 的负载部分无法通过较早的负载。乍一看,RMW 的加载部分可能会传递写入缓冲区中较早的存储
所以他们指的是在写入缓冲区中相互交叉的负载/存储(我认为这与存储缓冲区相同?)
谢谢
由于存储负载转发,某些负载指令能否在全局范围内不可见?换句话说,如果加载指令从存储缓冲区中获取其值,则它永远不必从高速缓存中读取。
通常说来,当从L1D缓存读取负载时,该负载在全局范围内可见,因此,未从L1D读取的负载应使其在全局上不可见。
x86 ×6
assembly ×4
c++ ×3
concurrency ×2
memory-model ×2
stdatomic ×2
atomic ×1
cpu ×1
cpu-cache ×1
intel ×1
lock-free ×1
performance ×1
volatile ×1