挥发性使用的可重现的例子

Den*_*nis 11 .net c# multithreading

我正在寻找一个可重现的示例,可以演示volatile关键字的工作原理.我正在寻找一些工作"错误"的东西,而没有标记为易变的变量,并且"正确"地使用它.

我的意思是一些示例,它将证明执行期间的写/读操作顺序与变量未标记为volatile时的预期顺序不同,并且当变量未标记为volatile时不同.

我认为我得到了一个例子但是在其他人的帮助下我意识到它只是一段错误的多线程代码.为什么volatile和MemoryBarrier不会阻止操作重新排序?

我还发现了一个链接,它演示了volatile对优化器的影响,但它与我正在寻找的不同.它表明对标记为volatile的变量的请求不会被优化.如何在C#中说明volatile关键字的用法

这是我到目前为止的地方.此代码未显示任何读/写操作重新排序的迹象.我正在寻找一个会展示的.

    using System;
    using System.Threading;
    using System.Threading.Tasks;
    using System.Runtime.CompilerServices;

    namespace FlipFlop
    {
        class Program
        {
            //Declaring these variables 
            static byte a;
            static byte 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++;
                    a = 0;
                    b = 0;
                    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)
                {
                    WriteInOneDirection();
                    WriteInOtherDirection();

                    //Stops spinning in the Check method.
                    continueChecking = false;
                }
            }

            [MethodImpl(MethodImplOptions.NoInlining)]
            static void WriteInOneDirection(){
                a = 1;
                b = 10;
            }

            [MethodImpl(MethodImplOptions.NoInlining)]
            static void WriteInOtherDirection()
            {
                b = 20;
                a = 2;
            }

            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;
                    }
                }
            }
        }
    }
Run Code Online (Sandbox Code Playgroud)

编辑:

据我所知,导致重新排序的优化可以来自JITer或来自硬件本身.我可以改写一下我的问题.JITer或x86 CPU是否重新排序读/写操作并且有没有办法在C#中演示它?

Han*_*ant 17

volatile的确切语义是抖动实现细节.编译器发出Opcodes.Volatile IL指令,您可以访问声明为volatile的变量.它进行了一些检查以验证变量类型是否合法,您不能声明大于4字节volatile的值类型,但这是buck停止的位置.

C#语言规范定义的行为挥发性,这里引述的埃里克利珀.'release'和'acquire'语义只对具有弱内存模型的处理器内核才有意义.这类处理器在市场上表现不佳,可能是因为它们是如此巨大的编程难题.你的代码在Titanium上运行的几率很小.

C#语言规范定义的特别之处在于它根本没有提到真正发生的事情.声明变量volatile会阻止抖动优化器优化代码以将变量存储在cpu寄存器中.这就是为什么Marc链接的代码悬而未决.这只会发生在当前的x86抖动中,这是另一个强烈的暗示,即volatile实际上是一个抖动实现细节.

volatile的语义不佳有着悠久的历史,它来自C语言.谁的代码生成器也很难解决问题.这是一篇有趣的报道(pdf).它的历史可以追溯到2008年,这是一个30年以上的良好机会.或者错误,当代码优化器忘记变量是易失性时,这会变得很糟糕.未经优化的代码永远不会有问题.值得注意的是,"开源"版.NET(SSLI20)中的抖动完全忽略了IL指令.还可以说x86抖动的当前行为是一个错误.我认为,将其推入故障模式并不容易.但没人能说它实际上一个错误.

写入是在墙上,只有在存储在存储器映射寄存器中时才声明变量volatile.关键字的初衷.您在C#语言中遇到此类用法的几率应该非常小,类似的代码属于设备驱动程序.最重要的是,永远不要认为它在多线程场景中很有用.

  • 好吧,我做到了.首先找到一个带有弱内存模型的CPU的机器.一个带有Itanitum的人不应该太难找到废料堆.等到冬天,我听说它们是很好的空间加热器.我认为,ARM内核也很薄弱,它将采用具有双核CPU的Windows 7手机.我认为你也必须等待,微软的iPad克隆可能是第一个. (2认同)

Bri*_*eon 6

您可以使用此示例来演示使用和不使用 的不同行为volatile此示例必须使用发布版本进行编译,并在调试器1之外运行。通过volatilestop标志中添加和删​​除关键字来进行实验。

这里发生的是,读stopwhile循环重新排序,以便它如果循环之前发生volatile被省略。即使在主线程将stop标志设置为true.

class Program
{
    static bool stop = false;

    public static void Main(string[] args)
    {
        var t = new Thread(() =>
        {
            Console.WriteLine("thread begin");
            bool toggle = false;
            while (!stop)
            {
                toggle = !toggle;
            }
            Console.WriteLine("thread end");
        });
        t.Start();
        Thread.Sleep(1000);
        stop = true;
        Console.WriteLine("stop = true");
        Console.WriteLine("waiting...");

        // The Join call should return almost immediately.
        // With volatile it DOES.
        // Without volatile it does NOT.
        t.Join(); 
    }
}
Run Code Online (Sandbox Code Playgroud)

还应注意,对本示例的细微更改会降低其可重复性的可能性。例如,添加Thread.Sleep(可能是为了模拟线程交错)本身会引入内存屏障,从而引入volatile关键字的类似语义。我怀疑Console.WriteLine引入了隐式内存屏障或以其他方式阻止抖动使用指令重新排序操作。如果您开始过多地弄乱示例,请记住这一点。


1我相信 2.0 之前的框架版本不包括这种重新排序优化。这意味着您应该能够在 2.0 及更高版本中重现此行为,但不能在早期版本中重现。


归档时间:

查看次数:

984 次

最近记录:

7 年,10 月 前