为什么即使在使用Monitor时,不是所有成员变量都需要volatile以确保线程安全?(为什么该模型真正起作用?)

用户名

(我知道他们没有,但是我正在寻找不使用volatile就能真正工作的根本原因,因为应该没有什么可以阻止编译器在没有volatile的情况下将变量存储在寄存器中...或者在那里...)

这个问题源于一种思想上的分歧,即不使用volatile编译器(理论上可以以各种方式对任何变量进行优化,包括将其存储在CPU寄存器中。)文档中说,使用诸如锁变量的同步时并不需要这样做。但是实际上在某些情况下,似乎编译器/ jit无法知道您是否会在代码路径中使用它们。因此,使存储模型“起作用”的确是在发生其他事情。

在此示例中,是什么阻止编译器/ Jit将_count优化到寄存器中,从而使增量在寄存器上完成,而不是直接在内存中完成(在退出调用之后写入内存)?如果_count是易失性的,似乎一切都应该没问题,但是编写的许多代码都没有易失性。如果编译器在方法中看到锁或同步对象,则编译器可能会知道不优化_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
// ...
// ...
}
用户名

这个问题的最佳猜测似乎是在调用任何函数之前,将存储在CPU寄存器中的所有变量都保存到内存中。这是有道理的,因为从单个线程的编译器设计角度出发将要求这样做,否则,如果该对象被其他函数/方法/对象使用,则该对象可能看起来不一致。因此,可能不像某些人/文章所声称的那样,同步对象/类是由编译器检测到的,并且非易失性变量通过它们的调用是安全的。(也许是在同一方法中使用锁或其他同步对象时,但是一旦您在另一个方法中调用了可能不调用这些同步对象的方法),取而代之的是,仅调用另一种方法就足以将存储在CPU寄存器中的值保存到内存中。因此,不需要所有变量都是易变的。

我也怀疑,其他人也怀疑,由于某些线程问题,类的字段没有得到尽可能多的优化。

一些注意事项(我的理解):Thread.MemoryBarrier()主要是CPU指令,以确保从CPU角度来看,写/读操作不会绕过该障碍。(这与存储在寄存器中的值没有直接关系)因此,这可能不是直接导致将变量从寄存器保存到内存的原因(除了根据我们在此讨论的方法调用这一事实,很可能会导致这种情况发生) -可能实际上是任何方法调用,尽管可能会影响从寄存器保存的所有使用过的类字段)

从理论上讲,JIT /编译器也可以将该方法纳入考虑范围,以确保从CPU寄存器存储变量。但是,只要遵循我们对任何其他方法或类的任何调用的简单提议规则,就会将存储在寄存器中的变量保存到内存中。另外,如果有人将该调用包装在另一个方法(可能是很多方法)中,则编译器将不太可能分析该方法来推测执行。JIT可以做一些事情,但是同样,它可能不会深入分析,并且两种情况都需要确保锁定/同步工作无论如何,因此最简单的优化可能就是答案。

除非我们有任何人编写可以证实其全部猜测的编译器,否则可能是关于为什么不需要volatile的最好猜测。

如果遵循该规则,则同步对象在进入和离开时仅需要使用自己对MemoryBarrier的调用,以确保CPU从其写缓存中获取最新的值,以便刷新它们,以便可以读取正确的值。在此站点上,您将看到建议的隐式内存障碍:http : //www.albahari.com/threading/part4.aspx

本文收集自互联网,转载请注明来源。

如有侵权,请联系[email protected] 删除。

编辑于
0

我来说两句

0条评论
登录后参与评论

相关文章

Related 相关文章

热门标签

归档