Warning: file_get_contents(/data/phpspider/zhask/data//catemap/2/csharp/300.json): failed to open stream: No such file or directory in /data/phpspider/zhask/libs/function.php on line 167

Warning: Invalid argument supplied for foreach() in /data/phpspider/zhask/libs/tag.function.php on line 1116

Notice: Undefined index: in /data/phpspider/zhask/libs/function.php on line 180

Warning: array_chunk() expects parameter 1 to be array, null given in /data/phpspider/zhask/libs/function.php on line 181

Warning: file_get_contents(/data/phpspider/zhask/data//catemap/2/.net/20.json): failed to open stream: No such file or directory in /data/phpspider/zhask/libs/function.php on line 167

Warning: Invalid argument supplied for foreach() in /data/phpspider/zhask/libs/tag.function.php on line 1116

Notice: Undefined index: in /data/phpspider/zhask/libs/function.php on line 180

Warning: array_chunk() expects parameter 1 to be array, null given in /data/phpspider/zhask/libs/function.php on line 181
C# 为什么volatile和MemoryBarrier不能阻止操作重新排序?_C#_.net_Multithreading - Fatal编程技术网

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

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

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

每次我运行它时,它都会捕获写入操作的重新排序。不管我是在调试还是发布中运行它。无论我是作为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;
     ...
我认为这种情况是可能的:

  • int tempA=a使用最后一个循环的值执行(a==2)
  • 有一个到写入线程的上下文开关
  • b=10
    循环停止
  • 有一个到检查线程的上下文开关
  • int tempB=b以b==10执行

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

    结果与重新排序、内存限制或易失性无关。所有这些构造都需要避免编译器或CPU指令重新排序的影响

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

    首先,请注意,将并行启动多个
    Write()
    任务。由于Write()中的lock(),它们是按顺序运行的,但是single
    Check()
    方法可以读取
    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
    中使用呢?Put
    Thread.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;