为什么volatile和MemoryBarrier不会阻止操作重新排序?

如果我正确理解volatile和MemoryBarrier的含义,那么下面的程序永远无法显示任何结果。

它每次运行时都会捕获写操作的重新排序。 如果我在Debug或Release中运行它并不重要。 如果我将它作为32位或64位应用程序运行也没关系。

为什么会这样?

using System; using System.Threading; using System.Threading.Tasks; namespace FlipFlop { class Program { //Declaring these variables as volatile should instruct compiler to //flush all caches from registers into the memory. static volatile int a; static volatile int b; //Track a number of iteration that it took to detect operation reordering. static long iterations = 0; static object locker = new object(); //Indicates that operation reordering is not found yet. static volatile bool continueTrying = true; //Indicates that Check method should continue. static volatile bool continueChecking = true; static void Main(string[] args) { //Restarting test until able to catch reordering. while (continueTrying) { iterations++; var checker = new Task(Check); var writter = new Task(Write); lock (locker) { continueChecking = true; checker.Start(); } writter.Start(); checker.Wait(); writter.Wait(); } Console.ReadKey(); } static void Write() { //Writing is locked until Main will start Check() method. lock (locker) { //Using memory barrier should prevent opration reordering. a = 1; Thread.MemoryBarrier(); b = 10; Thread.MemoryBarrier(); b = 20; Thread.MemoryBarrier(); a = 2; //Stops spinning in the Check method. continueChecking = false; } } static void Check() { //Spins until finds operation reordering or stopped by Write method. while (continueChecking) { int tempA = a; int tempB = b; if (tempB == 10 && tempA == 2) { continueTrying = false; Console.WriteLine("Caught when a = {0} and b = {1}", tempA, tempB); Console.WriteLine("In " + iterations + " iterations."); break; } } } } } 

我不认为这是重新订购。

这段代码根本不是线程安全的:

  while (continueChecking) { int tempA = a; int tempB = b; ... 

我认为这种情况是可能的:

  1. int tempA = a; 使用最后一个循环的值执行(a == 2)
  2. Write线程有一个上下文切换
  3. b = 10 ,循环停止
  4. Check线程有一个上下文切换
  5. int tempB = b; 以b == 10执行

我注意到对MemoryBarrier()的调用增加了这种情况的可能性。 可能是因为它们会导致更多的上下文切换。

你不是在测试之间清理变量,所以(除了第一个之外的所有变量)最初a2b20 Write完成任何事情 之前

Check可以获得a 初始值(因此tempA2 ),然后Write可以进入,直到将b更改为10

现在Check读取b (因此tempB10 )。

瞧瞧。 重新订购无需重新订购。

在运行之间将ab重置为0 ,我预计它会消失。

编辑:已确认; “按原样”我几乎立即得到了这个问题(<2000次迭代); 但通过添加:

 while (continueTrying) { a = b = 0; // reset <======= added this 

它然后循环任何时间没有任何问题。

或作为流程:

 Write A= B= Check (except first run) 2 20 int tempA = a; a = 1; 1 20 Thread.MemoryBarrier(); b = 10; 1 10 int tempB = b; 

结果与重新排序,记忆障碍或易失性无关。 需要所有这些结构以避免编译器或CPU重新排序指令的影响。

但是,即使假设完全一致的单CPU内存模型并且没有编译器优化,该程序也会产生相同的结果。

首先,请注意将并行启动多个Write()任务。 由于Write()内部的lock(),它们按顺序运行,但是一个Signle Check()方法可以读取由Write()任务的不同实例产生的ab

因为Check()函数与Write函数没有同步 – 它可以在两个任意和不同的时刻读取ab 。 您的代码中没有任何内容阻止Check()在一个时刻读取前一个Write()生成的内容,然后在另一个时刻读取由Write()生成的b 。 首先,你需要在Check()同步(锁定),然后你可能 (但可能不在这种情况下)需要内存屏障和volatile以解决内存模型问题。

这就是你所需要的:

  int tempA, tempB; lock (locker) { tempA = a; tempB = b; } 
  1. 如果您在MemoryBarrier中使用MemoryBarrier ,为什么不在checker执行此操作? 把Thread.MemoryBarrier();int tempA = a;之前int tempA = a;

  2. 调用Thread.MemoryBarrier(); 这么多次阻止了该方法的所有优点。 在a = 1;之前或之后只调用一次a = 1;