C# 为什么volatile和MemoryBarrier不能阻止操作重新排序?
如果我正确理解volatile和MemoryBarrier的含义,下面的程序就永远无法显示任何结果 每次我运行它时,它都会捕获写入操作的重新排序。不管我是在调试还是发布中运行它。无论我是作为32位还是64位应用程序运行它,这也无关紧要 为什么会这样C# 为什么volatile和MemoryBarrier不能阻止操作重新排序?,c#,.net,multithreading,C#,.net,Multithreading,如果我正确理解volatile和MemoryBarrier的含义,下面的程序就永远无法显示任何结果 每次我运行它时,它都会捕获写入操作的重新排序。不管我是在调试还是发布中运行它。无论我是作为32位还是64位应用程序运行它,这也无关紧要 为什么会这样 using System; using System.Threading; using System.Threading.Tasks; namespace FlipFlop { class P
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;
...
我认为这种情况是可能的:
int tempA=a代码>使用最后一个循环的值执行(a==2)
b=10
循环停止int tempB=b代码>以b==10执行
我注意到对MemoryBarrier()的调用增加了出现这种情况的可能性。可能是因为它们会导致更多的上下文切换 结果与重新排序、内存限制或易失性无关。所有这些构造都需要避免编译器或CPU指令重新排序的影响 但是,即使假设完全一致的单CPU内存模型并且没有编译器优化,这个程序也会产生相同的结果 首先,请注意,将并行启动多个
Write()
任务。由于Write()中的lock(),它们是按顺序运行的,但是singleCheck()
方法可以读取a
和b
由Write()
任务的不同实例生成的
由于Check()
函数与Write
函数没有同步,因此它可以在两个任意不同的时刻读取a
和b
。您的代码中没有任何内容可以阻止Check()
在某一时刻读取前一次Write()
生成的a
,然后在另一时刻读取后续Write()
生成的b
。首先,您需要在Check()
中进行同步(锁定),然后您可能(但在本例中可能不需要)需要内存屏障和volatile来解决内存模型问题
这就是您所需要的:
int tempA, tempB;
lock (locker)
{
tempA = a;
tempB = b;
}
writer
中使用MemoryBarrier
,为什么不在checker
中使用呢?PutThread.MemoryBarrier()代码>在int tempA=a之前代码>
Thread.MemoryBarrier()代码>多次阻止了该方法的所有优点。在a=1之前或之后只调用一次代码>
您没有在测试之间清理变量,因此(对于除第一个之外的所有变量)在
写入之前,最初a
是2
,b
是20
-
Check
可以获得a
的初始值(因此tempA
是2
),然后Write
可以进入,甚至可以将b
更改为10
现在,Check
读取b
(因此tempB
是10
)
瞧。无需重新订购
在两次运行之间将a
和b
重置为0
,我希望它会消失
编辑:确认;“按原样”我几乎马上就知道了问题(b/c这正是他们的想法。内存障碍(写)只是确保目前为止的所有操作都已刷新,因此以下操作是按障碍的一部分排序的。代码中最有趣的事情是删除所有线程。MemoryBarrier();
行可以修复问题=)@米坎特:不,那不能解决问题。这只会让事情变得非常不可能。让它运行几天,它仍然有可能发生。它看起来比这更有趣。如果没有重新排序,什么场景会给出tempA/tempB的值?注意,每个测试只有一次写操作(锁只是为了延迟访问,所以写操作不会发生得太快;实际上,它不一定会这样做,因为在启动和实际启动之间可能会有延迟-但似乎已经足够接近了)@Marc-检查程序在写入之前启动,因此,它有机会观察作家的所有作品。Writer中的MemoryBarrier只会让事情变得更糟,因为它增加了Checker查看所有中间值的更改OP并不是试图讨论互斥-它实际上是专注于预期的重新排序预防。最后的“锁”建议真的错过了他试图说明的内容,我想再次说明,问题中的锁不是为了提供互斥;它只是想延迟写入,直到读卡器启动。“将有多个Write()任务并行启动”——这是非常错误的。1编写/迭代。这并不能真正解释发生了什么。这些建议如何解决.NET内存模型方面的问题?@dtb在你编辑我的帖子并删除一行之前,我的帖子可能是@Dennis的线索,这一点更清楚了。。。他的密码里没有什么神秘的事情发生。NET内存模型也没有问题。一切都是书面的。因此,我认为丹尼斯能够根据我的文章得到问题的答案。也许丹尼斯能够根据你的线索得到答案,但你为什么不直接提供答案给大家看呢?@Dennis没问题;我想他删除了,然后又取消了,所以它不在那里。接受他的建议是正确的做法,避免你怀疑自己并删除?@Marc:我有正确的答案/见解,但在第二次阅读时,我被自己的一个打字错误弄糊涂了:}。
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;