我正在寻找一个可以演示 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/