编辑:欢迎反对者评论一两句话
在 C#10 in a Nutshell 中,Joseph Albahari 写道:
内存屏障是一道“栅栏” 重新排序和缓存的影响无法渗透到其中。
这不仅适用于锁,还适用于所有同步结构。 因此,如果您使用信号构造,例如,确保 一次只有一个线程读取/写入一个变量,您不需要 锁。因此,以下代码是线程安全的,无需锁定 x
给出这个例子时:
var signal = new ManualResetEvent (false);
int x = 0;
new Thread (() => { x++; signal.Set(); }).Start();
signal.WaitOne();
Console.WriteLine (x); // 1 (always)
x++
不应该是线程安全的(这个问题)。
我从上面的引用中得到的是,通过在代码中隐式执行此操作,x++ 变得线程安全:
Thread.MemoryBarrier();
x++;
Thread.MemoryBarrier();
我的问题是:
内存屏障如何通过防止重新排序和缓存来真正实现线程安全?
所有隐式 MemoryBarriers() 到底应该放在代码示例中的什么位置?
内存屏障如何通过防止重新排序和缓存来真正实现线程安全?
在 Joseph Albahari 的示例中,隐式内存屏障本身并不能实现线程安全。它们是更大机制的一部分,其中还包括信号发送。移除障碍或信号,你就会留下一个有缺陷的机制。为了获得理想的线程安全性,两个组件应该共存。该信号确保一个线程将等待,直到另一个线程发送已完成递增
x
的信号。内存屏障确保编译器/抖动/处理器不会尝试通过重新排序指令并将它们从屏障的一侧移动到另一侧来优化操作。信令与线程有关,屏障与内存访问有关。
所有隐式
到底应该放在代码示例中的什么位置?MemoryBarriers()
有两个障碍。
signal.Set();
之前,以防止编译器/抖动/处理器对 x++
指令重新排序,并将它们移到 signal.Set();
之后。signal.WaitOne();
之后,以防止编译器/抖动/处理器重新排序x
的读取(在Console.WriteLine(x);
中),并将其移到signal.WaitOne();
之前。