需要澄清有关 Thread.MemoryBarrier() 的信息
本文关键字:MemoryBarrier 信息 Thread | 更新日期: 2023-09-27 18:14:33
可能的重复项:
为什么我们需要 Thread.MemoryBarrier((?
简而言之,来自O'Reilly的C#:
class Foo
{
int _answer;
bool _complete;
void A()
{
_answer = 123;
Thread.MemoryBarrier(); // Barrier 1
_complete = true;
Thread.MemoryBarrier(); // Barrier 2
}
void B()
{
Thread.MemoryBarrier(); // Barrier 3
if (_complete)
{
Thread.MemoryBarrier(); // Barrier 4
Console.WriteLine (_answer);
}
}
}
假设方法 A 和 B 在不同的线程上并发运行:
作者说:"障碍 1 和障碍 4 阻止此示例写入"0"。障碍 2 和障碍 3 提供新鲜度保证:它们确保如果 B 在 A 之后运行,阅读_complete将评估到真的。
我的问题是:
- 为什么需要屏障4?障碍 1 还不够?
- 为什么需要2和3?
- 据我了解,屏障阻止在遵循指令后在其位置之前执行指令,我说得对吗?
内存屏障对从内存读取和写入强制实施排序约束:屏障发生之前的内存访问操作 - 屏障之后的内存访问操作。
- 屏障 1
和障碍 4 具有互补的作用:屏障 1 确保写入
_answer
发生在写入_complete
之前,而屏障 4 确保从_complete
读取之前发生读取_answer
。想象一下障碍 4 不存在,但障碍 1 在那里。虽然保证在写入true
之前将123
写入_answer
以_complete
其他正在运行的线程B()
可能仍会重新排序其读取操作,因此它可能在读取_complete
之前读取_answer
。类似地,如果屏障 1 被移除并保留屏障 4:虽然从B()
中的_complete
读取总是会发生 - 在从_answer
读取之前,_complete
仍然可以在_answer
之前被运行A()
的其他线程写入。
屏障 2 和 3 提供新鲜度保证:如果屏障 3 在屏障 2 之后执行,则在执行屏障 2 时运行
A()
的线程可见的状态对运行B()
执行屏障 3 的线程可见。如果没有这两个障碍中的任何一个,B()
完成后执行A()
可能不会看到A()
所做的更改。特别是屏障 2 阻止写入_complete
的值被运行A()
的处理器缓存,并强制处理器将其写出到主内存。同样,屏障 3 阻止运行B()
的处理器依赖缓存来获取_complete
强制从主内存读取的值。但请注意,在没有内存障碍 2 和 3 的情况下,过时的缓存并不是唯一会阻止新鲜度保证的因素。内存总线上的操作重新排序是这种机制的另一个例子。
内存屏障只是确保内存访问操作的效果跨屏障排序。其他指令(例如递增寄存器中的值(仍可重新排序。
好的,我们开始:内存屏障可防止优化编译器对指令重新排序。这意味着在屏障之前的任何指令都不能在遵循屏障的指令之后执行。有几种类型的障碍,但我不会详细介绍。此外,内存排序较弱的 CPU 可以对指令重新排序并可能造成死锁。所以:
- 需要屏障 4 来使线程运行方法 B 读取 _answer 的最新值(即读取 123 而不是 0(。如果在发布模式下编译,编译器可能会优化代码并重新排序指令,以便运行 B 的线程可以读取 0,即使您编写的指令在逻辑上使这不可能(因为_answer是在 _complete 之前分配的(。
- 屏障 2 和 3 还阻止重新排序(以及缓存 _complete 的值(,因此运行 B 的线程不可能将_complete读取为 false,前提是它在 A 之后运行。
- 答案在上面。