为什么即使使用Monitor,所有成员变量也不需要易变的线程安全性?(为什么模型真的有效?)

use*_*937 2 c# multithreading volatile memory-barriers

(我知道他们没有,但我正在寻找其实际工作的根本原因,而不使用volatile,因为没有什么可以防止编译器将变量存储在没有volatile的寄存器中......或者是......)

这个问题源于思想的不和谐,即没有易失性的编译器(理论上可以通过各种方式优化任何变量,包括将其存储在CPU寄存器中.)虽然文档说在使用锁定变量等同步时不需要.但实际上在某些情况下,编译器/ jit似乎无法知道您是否会在代码路径中使用它们.所以怀疑是在这里真正发生的事情,使记忆模型"工作".

在这个例子中,什么阻止编译器/ jit优化_count到一个寄存器,从而在寄存器上完成增量而不是直接到存储器(稍后在退出调用后写入内存)?如果_count是易变的,那么看起来一切都应该没问题,但很多代码都是在没有volatile的情况下编写的.如果在方法中看到锁定或同步对象,编译器可能知道不会将_count优化到寄存器中,但在这种情况下,锁定调用是在另一个函数中.

大多数文档都说如果使用锁等同步调用,则不需要使用volatile.

那么是什么阻止了编译器优化_count到寄存器并可能只更新锁中的寄存器?我有一种感觉,大多数成员变量都不会因为这个原因被优化到寄存器中,因为每个成员变量都需要是易变的,除非编译器告诉它不应该优化(否则我怀疑大量的代码会失败) .我看到类似的东西,看看C++多年前本地函数变量存储在寄存器中,类成员变量没有.

所以主要的问题是,它是否真的是没有volatile的唯一方法,编译器/ jit不会将类成员变量放在寄存器中,因此不需要volatile?

(请忽略调用中缺少异常处理和安全性,但你得到了要点.)

public class MyClass
{
  object _o=new object();

  int _count=0;

  public void Increment()
  {
    Enter();
    // ... many usages of count here...
    count++;
    Exit();
  }




//lets pretend these functions are too big to inline and even call other methods 
// that actually make the monitor call (for example a base class that implemented these) 
  private void Enter() { Monitor.Enter(_o); }  
  private void Exit()  { Monitor.Exit(_o); }  //lets pretend this function is too big to inline
// ...
// ...
}
Run Code Online (Sandbox Code Playgroud)

omi*_*nug 5

进入和离开一个Monitor导致完整的记忆围栏.因此,CLR确保所有其他线程在Monitor.Enter/ 之前的所有写入操作Monitor.Exit以及方法调用之后的所有读取操作都"发生"之后.这也意味着在调用之后调用之前的语句不能移动,反之亦然.

http://www.albahari.com/threading/part4.aspx.