当其中一个变量包含在另一个变量中时,可以重新排序后续写入吗?

时间:2018-04-19 12:24:32

标签: c# multithreading concurrency compiler-optimization memory-barriers

据我所知,出于性能原因,.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]); 

这种情况是需要由lockvolatileThread.MemoryBarrier()加以保护,还是这样安全?

我强烈怀疑这是安全的,但第一种情况有什么不同?

1 个答案:

答案 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代替仅使该特定写操作易失。