像我这样的问题已经被问过,但我的有点不同。问题是,“为什么 C#
中不允许在 System.Double
和 System.Int64
等类型上使用 volatile 关键字?”
我第一反应是回答我的同事:“嗯,在 32 位机器上,这些类型至少需要两次滴答才能进入处理器,而 .Net 框架的目的是抽象出像这样的特定于处理器的细节。 ”他对此回应道:“如果由于特定于处理器的问题而阻止您使用某个功能,那么它就不是抽象的!”
他的意思是,特定于处理器的细节不应该向使用从程序员那里“抽象”此类细节的框架的人显示。因此,框架(或 C#)应该抽象掉这些,并做它需要做的事情,为 System.Double 等提供相同的保证(无论是信号量、内存屏障还是其他) 。我认为框架不应该在 易失性上添加信号量的开销,因为程序员不希望这样的关键字产生这样的开销,因为信号量对于 32 位来说不是必需的类型。 64 位类型的更大开销可能会让人感到惊讶,因此,.Net 框架最好不要允许它,并且在开销可以接受的情况下让您在更大的类型上使用自己的信号量。
这导致我们调查 volatile 关键字的含义。 (请参阅此页面)。该页面在注释中指出:
在 C# 中,在字段上使用 volatile 修饰符可保证对该字段的所有访问都使用 VolatileRead 或 VolatileWrite。
嗯......VolatileRead
和 VolatileWrite
都支持我们的 64 位类型!那么我的问题是
“为什么 C#
中不允许在 System.Double
和 System.Int64
等类型上使用 volatile 关键字?”
A question like mine has been asked, but mine is a bit different. The question is, "Why is the volatile keyword not allowed in C#
on types System.Double
and System.Int64
, etc.?"
On first blush, I answered my colleague, "Well, on a 32-bit machine, those types take at least two ticks to even enter the processor, and the .Net framework has the intention of abstracting away processor-specific details like that." To which he responds, "It's not abstracting anything if it's preventing you from using a feature because of a processor-specific problem!"
He's implying that a processor-specific detail should not show up to a person using a framework that "abstracts" details like that away from the programmer. So, the framework (or C#) should abstract away those and do what it needs to do to offer the same guarantees for System.Double
, etc. (whether that's a Semaphore, memory barrier, or whatever). I argued that the framework shouldn't add the overhead of a Semaphore on volatile
, because the programmer isn't expecting such overhead with such a keyword, because a Semaphore isn't necessary for the 32-bit types. The greater overhead for the 64-bit types might come as a surprise, so, better for the .Net framework to just not allow it, and make you do your own Semaphore on larger types if the overhead is acceptable.
That led to our investigating what the volatile keyword is all about. (see this page). That page states, in the notes:
In C#, using the volatile modifier on a field guarantees that all access to that field uses VolatileRead or VolatileWrite.
Hmmm.....VolatileRead
and VolatileWrite
both support our 64-bit types!! My question, then, is,
"Why is the volatile keyword not allowed in C#
on types System.Double
and System.Int64
, etc.?"
发布评论
评论(5)
如果您使用易失性字段、显式内存屏障等低锁技术,那么您将完全陷入处理器特定细节的世界中。您需要深入地准确了解处理器在重新排序、一致性等方面允许做什么和不允许做什么,以便编写使用低锁技术的正确、可移植、健壮的程序。
此功能的要点是“我放弃单线程编程所保证的方便抽象,并通过深入了解处理器的特定实现知识来拥抱可能获得的性能增益。”当您开始使用低锁技术时,您应该期望可以使用的抽象更少,而不是更多的抽象。
你“全力以赴”大概是有原因的;您所付出的代价是必须应对所述金属的怪癖。
If you are using low-lock techniques like volatile fields, explicit memory barriers, and the like, then you are entirely in the world of processor-specific details. You need to understand at a deep level precisely what the processor is and is not allowed to do as far as reordering, consistency, and so on, in order to write correct, portable, robust programs that use low-lock techniques.
The point of this feature is to say "I am abandoning the convenient abstractions guaranteed by single-threaded programming and embracing the performance gains possible by having a deep implementation-specific knowledge of my processor." You should expect less abstractions at your disposal when you start using low-lock techniques, not more abstractions.
You're going "down to the metal" for a reason, presumably; the price you pay is having to deal with the quirks of said metal.
是的。原因是您甚至无法在一次操作中读取
double
或long
。我同意这是一种糟糕的抽象。我有一种感觉,原因是原子地读取它们需要付出努力,而且对于编译器来说太聪明了。因此,它们让您选择最佳解决方案:锁定
、互锁
等。有趣的是,它们实际上可以使用 MMX 寄存器在 32 位上原子读取。这就是 java JIT 编译器所做的事情。并且它们可以在 64 位机器上自动读取。所以我认为这是设计上的严重缺陷。
Yes. Reason is that you even can't read
double
orlong
in one operation. I agree that it is poor abstraction. I have a feeling that reason was that reading them atomically requires effort and it would be too smart for compiler. So they let you choose the best solution:lock
ing,Interlocked
, etc.Interesting thing is that they can actually be read atomically on 32 bit using MMX registers. This is what java JIT compiler does. And they can be read atomically on 64 bit machine. So I think it is serious flaw in design.
并不是你问题的真正答案,但是......
我很确定你引用的 MSDN 文档是不正确的,因为它指出“在字段上使用 volatile 修饰符保证对该字段的所有访问都使用VolatileRead 或 VolatileWrite"。
直接读取或写入
易失性
字段只生成半栅栏(读取时获取栅栏,写入时释放栅栏)。VolatileRead
和VolatileWrite
方法使用MemoryBarrier
在内部,生成一个完整的栅栏。Joe Duffy 对并发编程略知一二; 这是他对 volatile 的看法:
Not really an answer to your question, but...
I'm pretty sure that the MSDN documentation you've referenced is incorrect when it states that "using the volatile modifier on a field guarantees that all access to that field uses VolatileRead or VolatileWrite".
Directly reading or writing to a
volatile
field only generates a half-fence (an acquire-fence when reading and a release-fence when writing).The
VolatileRead
andVolatileWrite
methods useMemoryBarrier
internally, which generates a full-fence.Joe Duffy knows a thing or two about concurrent programming; this is what he has to say about volatile:
这是对遗产的简单解释。如果您阅读本文 - http://msdn.microsoft.com/en-au /magazine/cc163715.aspx,您会发现 .NET Framework 1.x 运行时的唯一实现是在 x86 计算机上,因此 Microsoft 针对 x86 内存模型实现它是有意义的。后来添加了 x64 和 IA64。因此基本内存模型始终是 x86 之一。
它可以在 x86 上实现吗?我实际上不确定它是否可以完全实现 - 从本机代码返回的 double 的引用可以对齐到 4 个字节而不是 8 个字节。在这种情况下,所有原子读/写的保证都不再成立。
It's a simple explanation of legacy. If you read this article - http://msdn.microsoft.com/en-au/magazine/cc163715.aspx, you'll find that the only implementation of the .NET Framework 1.x runtime was on x86 machines, so it makes sense for Microsoft to implement it against the x86 memory model. x64 and IA64 were added later. So the base memory model was always one of x86.
Could it have been implemented for x86? I'm actually not sure it can be fully implemented - a ref of a double returned from native code could be aligned to 4 bytes instead of 8. In which case, all your guarantees of atomic reads/writes no longer hold true.
从 .NET Framework 4.5 开始,现在可以使用 long 或
double
变量执行易失性读取或写入.microsoft.com/en-us/dotnet/api/system.threading.volatile.read" rel="nofollow noreferrer">Volatile.Read
和Volatile.Write
方法。这些方法对long
/double
变量执行原子读取和写入,从long/double
」") 变量执行原子读取和写入。 com/dotnet/runtime/blob/main/src/libraries/System.Private.CoreLib/src/System/Threading/Volatile.cs#L88-L96" rel="nofollow noreferrer">它们的实现:它们的原子性也记录:
不过,使用这两种方法并不像 volatile 关键字那么方便。需要注意的是,不要忘记将 volatile 字段的每个读/写访问分别包装在
Volatile.Read
或Volatile.Write
中。Starting from .NET Framework 4.5, it is now possible to perform a volatile read or write on
long
ordouble
variables by using theVolatile.Read
andVolatile.Write
methods. These methods perform atomic reads and writes on thelong
/double
variables, as it's evident from their implementation:Their atomicity is documented too:
Using these two methods is not as convenient as the
volatile
keyword though. Attention is required to not forget wrapping every read/write access of the volatile field inVolatile.Read
orVolatile.Write
respectively.