为什么不在System.Double和System.Long上使用volatile?

Lim*_*ent 40 .net c# multithreading volatile

有人问过像我这样的问题,但我的情况有点不同.现在的问题是,"为什么volatile关键字不允许C#在类型System.DoubleSystem.Int64等?"

乍一看,我回答了我的同事,"好吧,在32位机器上,这些类型至少需要两个滴答才能进入处理器,.Net框架的目的是抽象出像这样的处理器特定细节. " 他回答说:"如果由于处理器特定的问题导致你无法使用某个功能,那么它就不会抽象出任何东西!"

他暗示一个特定于处理器的细节不应该出现在使用框架的人身上,这个框架"抽象"远离程序员的细节.因此,框架(或C#)应该抽象出那些并做它需要做的事情来提供相同的保证System.Double等等(无论是信号量,内存障碍还是其他).我认为框架不应该增加信号量的开销volatile,因为程序员不期望使用这样的关键字这样的开销,因为32位类型不需要信号量.64位类型的更大开销可能会让人感到意外,因此,更好的.Net框架只是不允许它,并且如果开销是可接受的,那么就可以在更大的类型上使用自己的信号量.

这导致我们调查volatile关键字的全部内容.(见页).该页面在说明中说明:

在C#中,在字段上使用volatile修饰符可确保对该字段的所有访问都使用VolatileRead或VolatileWrite.

嗯..... VolatileRead并且VolatileWrite都支持我们的64位类型!! 那么我的问题是,

"为什么volatile关键字不允许C#在类型System.DoubleSystem.Int64等?"

Eri*_*ert 17

他暗示一个特定于处理器的细节不应该出现在使用框架的人身上,这个框架"抽象"远离程序员的细节.

如果您使用低锁技术(如易失性字段,显式内存屏障等),那么您完全处于特定于处理器的详细信息中.为了编写使用低锁技术的正确,可移植,强大的程序,您需要在深层精确地理解处理器是什么,并且不允许执行重新排序,一致性等等.

这个特性的要点是说"我放弃了单线程编程所保证的方便抽象,并通过深入了解我的处理器实现知识来获得性能提升." 当您开始使用低锁技术而不是更多抽象时,您应该期待更少的抽象.

大概是因为某种原因,你会"走向金属"; 你支付的价格是必须处理所述金属的怪癖.

  • @Andrey:当然,CLR允许我们对double类型的字段进行易失性,非原子访问(我想......还没试过).C#设计师决定不允许这样做; 所有易失性访问在C#中也是原子的.如果您是那种需要对包含大型结构的变量进行volatile但非原子访问的人,那么C#可能不是最适合您的语言.如果你想使用C#,你可以随时使用VolatileRead和VolatileWrite; 请注意,在实践中这样做*会导致完整的内存障碍,尽管这不是API的*保证*,并且可能在将来发生变化. (6认同)

And*_*rey 12

是.原因是你甚至无法阅读doublelong在一次操作中.我同意这是一个糟糕的抽象.我有一种感觉,理由是原子地阅读它们需要努力,而且对于编译器而言太聪明了.所以他们让你选择最好的解决方案:locking Interlocked,等等.

有趣的是,它们实际上可以使用MMX寄存器在32位上原子读取.这就是Java JIT编译器的功能.它们可以在64位机器上原子读取.所以我认为这是设计中的严重缺陷.

  • 关键是如果它们可以在CLI的_any_实现上以原子方式读/写. (3认同)
  • @Henk CLI的所有实现都有`System.Threading.Thread.VolatileRead`对吗?或者也许我没有得到你的意思. (3认同)
  • @Henk Holterman实际上是相当薄弱的一点.Java允许`long`和`double`上的`volatile`,它有比CLI更多的实现. (2认同)

Luk*_*keH 5

不是你的问题的答案,但......

我很确定你引用的MSDN文档在声明"在字段上使用volatile修饰符保证对该字段的所有访问都使用VolatileRead或VolatileWrite"时是不正确的.

直接读取或写入volatile字段只会产生半栅栏(读取时的获取栅栏和写入时的释放栅栏).

VolatileReadVolatileWrite方法使用MemoryBarrier在内部,其产生一个全栅栏.

Joe Duffy对并发编程知之甚少; 这是他对易变性的看法:

(顺便说一句,许多人想知道标记为volatile的变量的加载和存储之间的区别以及对Thread.VolatileRead和Thread.VolatileWrite的调用.区别在于前面的API实现比jitted代码更强:它们实现了获取/通过在右侧发出完整的栅栏来释放语义.调用API也更昂贵,但至少允许您根据呼叫站点来决定个别加载和存储需要MM保证.)