Joe Albahari在C#中解释volatile
有一篇非常好的文章:Threading in C#: PART 4: ADVANCED THREADING。
考虑到指令重新排序,Joe使用了这个例子:
public class IfYouThinkYouUnderstandVolatile
{
private volatile int x, y;
private void Test1() // Executed on one thread
{
this.x = 1; // Volatile write (release-fence)
int a = this.y; // Volatile read (acquire-fence)
}
private void Test2() // Executed on another thread
{
this.y = 1; // Volatile write (release-fence)
int b = this.x; // Volatile read (acquire-fence)
}
}
基本上他说的是a
和b
当方法在不同的线程上并行运行时,最终都会包含0
。
IOW优化器或处理器可以按如下方式重新排序指令:
public class IfYouThinkYouUnderstandVolatileReordered
{
private volatile int x, y;
private void Test1() // Executed on one thread
{
int tempY = this.y; // Volatile read (reordered)
this.x = 1; // Volatile write
int a = tempY; // Use the already read value
}
private void Test2() // Executed on another thread
{
int tempX = this.x; // Volatile read (reordered)
this.y = 1; // Volatile write (release-fence)
int b = tempX; // Use the already read value
}
}
虽然我们使用volatile
会发生这种情况的原因是写入指令之后的读取指令可以在写入之前移动指令
到目前为止,我了解这里发生了什么。
我的问题是:这个重新排序可以通过堆栈帧进行吗?我的意思是,在另一个方法(或属性访问器)中发生的易失性读取指令后,是否可以移动易失性写入指令?
查看以下代码:它使用属性而不是直接访问实例变量。
在这种情况下重新排序怎么样?无论如何都会发生吗?或者只有在编译器内联属性访问时才会发生这种情况?
public class IfYouThinkYouUnderstandVolatileWithProps
{
private volatile int x, y;
public int PropX
{
get { return this.x; }
set { this.x = value; }
}
public int PropY
{
get { return this.y; }
set { this.y = value; }
}
private void Test1() // Executed on one thread
{
this.PropX = 1; // Volatile write (release-fence)
int a = this.PropY; // Volatile read (acquire-fence)
}
private void Test2() // Executed on another thread
{
this.PropY = 1; // Volatile write (release-fence)
int b = this.PropX; // Volatile read (acquire-fence)
}
}
答案 0 :(得分:1)
你不应该考虑这么高级别的东西,因为你无法控制它们。 JIT有很多理由要内联或不内联。 重新排序是一个很好的概念,它允许您推断并行代码执行的可能结果。但真正发生的事情不仅仅是重新排序读/写操作。它可以是JIT对CPU寄存器中的实际重新排序或缓存值,或者CPU本身的推测性执行的影响,或者内存控制器如何完成其工作。
考虑读取和写入指针(或更少)大小的内存。使用此类读写的重新排序模型,并且不依赖于当前运行的JIT或CPU的具体细节。
答案 1 :(得分:1)
如ECMA-335所述
I.12.6.4优化
符合CLI的实现可以自由执行程序使用任何技术,在单个执行线程中保证线程生成的副作用和异常按照指定的顺序可见CIL。为此目的,只有易失性操作(包括易失性读取)构成可见的副作用。 (请注意,虽然只有易失性操作构成可见的副作用,但易失性操作也会影响非易失性引用的可见性。) 易失性操作在§I.12.6.7中规定。相对于另一个线程注入线程的异常没有排序保证(这种异常有时称为“异步异常”) (例如,System.Threading.ThreadAbortException)。
所以,显然它允许内联所有代码,然后它就像它一样。