c# - volatile 用法的可重现示例

标签 c# .net multithreading

我正在寻找一个可以演示 volatile 关键字如何工作的可重现示例。我正在寻找在没有标记为 volatile 的变量的情况下“错误”工作并且“正确”工作的东西。

我的意思是一些示例,该示例将证明执行期间的写入/读取操作顺序与变量未标记为 volatile 时的预期不同,并且在变量未标记为 volatile 时也没有不同。

我以为我得到了一个例子,但后来在其他人的帮助下我意识到它只是一段错误的多线程代码。 Why volatile and MemoryBarrier do not prevent operations reordering?

我还找到了一个链接,该链接演示了 volatile 对优化器的影响,但它与我正在寻找的不同。它表明对标记为 volatile 的变量的请求不会被优化。 How to illustrate usage of volatile keyword in C#

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

    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;
                    }
                }
            }
        }
    }

编辑:

据我了解,导致重新排序的优化可能来自 JITer 或硬件本身。我可以改述我的问题。 JITer 或 x86 CPU 是否会重新排序读/写操作,如果这样做,是否有一种方法可以在 C# 中进行演示?

最佳答案

volatile 的确切语义是抖动实现细节。编译器会在您访问声明为 volatile 的变量时发出 Opcodes.Volatile IL 指令。它会进行一些检查以验证变量类型是否合法,您不能将大于 4 个字节的值类型声明为 volatile,但这就是责任所在。

C# 语言规范定义了volatile 的行为,quoted here埃里克·利珀特着。 “释放”和“获取”语义仅在内存模型较弱的处理器内核上才有意义。这类处理器在市场上表现不佳,可能是因为它们的编程非常痛苦。您的代码在 Titanium 上运行的可能性微乎其微。

C# 语言规范定义的特别糟糕之处在于它根本没有提及真正 发生了什么。声明变量 volatile 可防止抖动优化器优化代码以将变量存储在 cpu 寄存器中。这就是 Marc 链接的代码挂起的原因。这只会发生在当前的 x86 抖动中,这是另一个强烈的暗示,表明 volatile 实际上是一个抖动实现细节。

volatile 的糟糕语义有着悠久的历史,它来自 C 语言。谁的代码生成器也很难做到正确。这是一个有趣的 report about it (pdf) .它可以追溯到 2008 年,这是一个 30 多年的好机会。或者错误的是,当代码优化器忘记变量是易变的时,这就会失败。未优化的代码永远不会有问题。值得注意的是,“开源”版本的 .NET (SSLI20) 中的抖动完全忽略了 IL 指令。也可以说 x86 抖动的当前行为是一个错误。我认为是的,将其撞入故障模式并不容易。但没有人可以争辩说它实际上是 一个错误。

写在墙上,只有在存储在内存映射寄存器中的变量才声明为 volatile。关键词的初衷。您在 C# 语言中遇到这种用法的几率应该微乎其微,像这样的代码属于设备驱动程序。最重要的是,从不假设它在多线程场景中有用。

关于c# - volatile 用法的可重现示例,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/6164466/

相关文章:

c# - 无法弄清楚这个 jQuery 代码是如何工作的

c# - Linq Where local counter closure 在 VS watch 中产生不同的结果

c# - 将音频文件流式传输到另一台计算机

c++ - cpp 中的线程给出错误

c# - 如何在 Xamarin.Forms 中制作平滑移动的音频 slider

c# - 你能有一个没有线程的 TCP 客户端吗

.net - Azure 服务总线主题中的基础 IOException

c# - 无法将 System.IO.Compression 添加到 SQL Server 中的受信任程序集

java - 使用 Java ExecutorService,如何完成主动执行的任务但停止等待任务的处理?

c# - 将 MvvmCross 与 dot42 一起使用