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

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

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

我认为我得到了一个例子但是在其他人的帮助下我意识到它只是一段错误的multithreading代码。 为什么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; } } } } } 

编辑:

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

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

C#语言规范定义了易失性行为,由Eric Lippert 引用 。 ‘release’和’acquire’语义只对具有弱内存模型的处理器内核才有意义。 这类处理器在市场上表现不佳,可能是因为它们是如此巨大的编程难题。 您的代码在Titanium上运行的几率很小。

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

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

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

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

这里发生的是, while循环中的stop的读取被重新排序,以便在循环之前发生,如果省略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(); } } 

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


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