据我所知,出于性能原因,.net框架编译器可能会将写入重新排序到不同的变量。
所以如果一个线程执行......
this.value=123;
this.initialized=true;
...然后在编写this.initialized==true
之前,另一个帖子可能会读this.value
。
为什么这不适用于以下情况?
如果一个线程执行...
int[] a= new int[1];
a[0] = 123;
this.array = a;
...那么在编写this.array!=null
之前,另一个帖子是否可以阅读a[0]
?
换句话说,这段代码是否只能打印" null"或" 123"?
int[] a = this._array;
if (a == null)
Console.Out.WriteLine("null");
else
Console.Out.WriteLine(a[0]);
这种情况是需要由lock
,volatile
,Thread.MemoryBarrier()
加以保护,还是这样安全?
我强烈怀疑这是安全的,但第一种情况有什么不同?
答案 0 :(得分:1)
让我们确定该段代码中的所有可能的内存操作,并理解它们之间的所有依赖关系。
temp1 = new int[1]; // (1) Calls a function that allocates an object.
int[] a = temp1; // (2) A possible write to a memory location of an atomic size.
a[0] = 123; // (3) A possible write to a memory location of an atomic size.
// The address of the target location depends on (2).
temp2 = a; // (4) A possible read from a memory location of an atomic size dependent on (2).
this.array = temp2; // (5) A possible write to a location of an atomic size dependent on (2).
重新排序(3)和(5)不会改变代码的单线程执行的行为。这两个写完全独立于单线程的角度。 a
持有对象引用的事实是无关紧要的。因此,C#内存模型允许重新排序这两个操作。
因此,如果您希望保留(3)和(5)的顺序,则需要在代码中明确指定。如果我们生成a
volatile
,那么(2)处的写入将具有释放语义,并且(4)处的读取将具有获取语义,这意味着(2)不能与先前的操作重新排序并且(4)不能与后续操作重新排序。然而,这个直到不会阻止(4)和(5)在(3)之前重新排序。如果我们改为this.array
volatile
,则写入(5)将具有释放语义,这意味着(5)不能与先前的操作(包括(3))重新排序。如果不需要对this.array
volatile进行所有访问,则可以使用Volatile.Write代替仅使该特定写操作易失。