如果我正确理解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;
}
}
}
}
}
答案 0 :(得分:10)
你没有清除测试之间的变量,所以(除了第一个之外的所有变量)最初a
是2
而b
是20
- 之前 Write
已完成任何。
Check
可以获得a
的 初始 值(因此tempA
为2
),然后Write
可以进入,将b
更改为10
。
现在Check
会读取b
(因此tempB
为10
)。
Et瞧。不需要重新订购才能重现。
在两次运行之间将a
和b
重置为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;
答案 1 :(得分:3)
我不认为这是重新订购。
这段代码根本不是线程安全的:
while (continueChecking)
{
int tempA = a;
int tempB = b;
...
我认为这种情况是可能的:
int tempA = a;
使用最后一个循环(a == 2)b = 10
并且循环停止int tempB = b;
以b == 10 我注意到对MemoryBarrier()的调用增加了这种情况的可能性。可能是因为它们会导致更多的上下文切换。
答案 2 :(得分:0)
结果与重新排序,记忆障碍或易失性无关。需要所有这些结构来避免编译器或CPU重新排序指令的影响。
但即使假设完全一致的单CPU内存模型并且没有编译器优化,该程序也会产生相同的结果。
首先,请注意将并行启动多个Write()
任务。它们由于Write()内部的lock()而按顺序运行,但是一个单一Check()
方法可以读取由a
任务的不同实例生成的b
和Write()
。
由于Check()
函数没有同步和Write
函数,因此它可以在两个任意且不同的时刻读取a
和b
。您的代码中没有任何内容阻止Check()
在某个时刻阅读之前a
生成的Write()
,然后在另一时刻阅读b
生成的Write()
。首先,您需要在Check()
中进行同步(锁定),然后可能(但在这种情况下可能不会)需要内存屏障和易失性来解决内存模型问题。
这就是你所需要的:
int tempA, tempB;
lock (locker)
{
tempA = a;
tempB = b;
}
答案 3 :(得分:-1)
如果您在MemoryBarrier
中使用writer
,为什么不在checker
中执行此操作?将Thread.MemoryBarrier();
放在int tempA = a;
之前。
多次调用Thread.MemoryBarrier();
会阻止该方法的所有优点。在a = 1;
之前或之后只调用一次。