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

发布于 2024-10-12 11:51:14 字数 1016 浏览 5 评论 0 原文

像我这样的问题已经被问过,但我的有点不同。问题是,“为什么 C# 中不允许在 System.DoubleSystem.Int64 等类型上使用 volatile 关键字?”

我第一反应是回答我的同事:“嗯,在 32 位机器上,这些类型至少需要两次滴答才能进入处理器,而 .Net 框架的目的是抽象出像这样的特定于处理器的细节。 ”他对此回应道:“如果由于特定于处理器的问题而阻止您使用某个功能,那么它就不是抽象的!”

他的意思是,特定于处理器的细节不应该向使用从程序员那里“抽象”此类细节的框架的人显示。因此,框架(或 C#)应该抽象掉这些,并做它需要做的事情,为 System.Double 等提供相同的保证(无论是信号量、内存屏障还是其他) 。我认为框架不应该在 易失性上添加信号量的开销,因为程序员不希望这样的关键字产生这样的开销,因为信号量对于 32 位来说不是必需的类型。 64 位类型的更大开销可能会让人感到惊讶,因此,.Net 框架最好不要允许它,并且在开销可以接受的情况下让您在更大的类型上使用自己的信号量。

这导致我们调查 volatile 关键字的含义。 (请参阅页面)。该页面在注释中指出:

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

嗯......VolatileReadVolatileWrite 都支持我们的 64 位类型!那么我的问题是

“为什么 C# 中不允许在 System.DoubleSystem.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.?"

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(5

菊凝晚露 2024-10-19 11:51:14

他暗示,特定于处理器的细节不应该向使用从程序员“抽象”此类细节的框架的人显示。

如果您使用易失性字段、显式内存屏障等低锁技术,那么您将完全陷入处理器特定细节的世界中。您需要深入地准确了解处理器在重新排序、一致性等方面允许做什么和不允许做什么,以便编写使用低锁技术的正确、可移植、健壮的程序。

此功能的要点是“我放弃单线程编程所保证的方便抽象,并通过深入了解处理器的特定实现知识来拥抱可能获得的性能增益。”当您开始使用低锁技术时,您应该期望可以使用的抽象更少,而不是更多的抽象。

你“全力以赴”大概是有原因的;您所付出的代价是必须应对所述金属的怪癖。

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.

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.

梦断已成空 2024-10-19 11:51:14

是的。原因是您甚至无法在一次操作中读取 doublelong 。我同意这是一种糟糕的抽象。我有一种感觉,原因是原子地读取它们需要付出努力,而且对于编译器来说太聪明了。因此,它们让您选择最佳解决方案:锁定互锁等。

有趣的是,它们实际上可以使用 MMX 寄存器在 32 位上原子读取。这就是 java JIT 编译器所做的事情。并且它们可以在 64 位机器上自动读取。所以我认为这是设计上的严重缺陷。

Yes. Reason is that you even can't read double or long 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: locking, 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.

镜花水月 2024-10-19 11:51:14

并不是你问题的真正答案,但是......

我很确定你引用的 MSDN 文档是不正确的,因为它指出“在字段上使用 volatile 修饰符保证对该字段的所有访问都使用VolatileRead 或 VolatileWrite"。

直接读取或写入 易失性 字段只生成半栅栏(读取时获取栅栏,写入时释放栅栏)。

VolatileReadVolatileWrite方法使用 MemoryBarrier 在内部,生成一个完整的栅栏。

Joe Duffy 对并发编程略知一二; 这是他对 volatile 的看法

(顺便说一句,很多人都想知道
负载和之间的差异
标记为易失性的变量存储
并调用 Thread.VolatileRead 和
Thread.VolatileWrite。区别
是以前的 API 是
实施比 jitted 更强
代码:他们实现获取/释放
通过发出完整的栅栏来实现语义
右侧。 API比较多
打电话也很贵,但至少
让您决定
逐个调用站点的基础上
单独的负载和商店需要
MM保证。)

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 and VolatileWrite methods use MemoryBarrier 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:

(As an aside, many people wonder about
the difference between loads and
stores of variables marked as volatile
and calls to Thread.VolatileRead and
Thread.VolatileWrite. The difference
is that the former APIs are
implemented stronger than the jitted
code: they achieve acquire/release
semantics by emitting full fences on
the right side. The APIs are more
expensive to call too, but at least
allow you to decide on a
callsite-by-callsite basis which
individual loads and stores need the
MM guarantees.)

桃扇骨 2024-10-19 11:51:14

这是对遗产的简单解释。如果您阅读本文 - 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.

翻了热茶 2024-10-19 11:51:14

从 .NET Framework 4.5 开始,现在可以使用 long 或 double 变量执行易失性读取或写入.microsoft.com/en-us/dotnet/api/system.threading.volatile.read" rel="nofollow noreferrer">Volatile.ReadVolatile.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">它们的实现

private struct VolatileIntPtr { public volatile IntPtr Value; }

[Intrinsic]
[NonVersionable]
public static long Read(ref long location) =>
#if TARGET_64BIT
    (long)Unsafe.As<long, VolatileIntPtr>(ref location).Value;
#else
    // On 32-bit machines, we use Interlocked,
    // since an ordinary volatile read would not be atomic.
    Interlocked.CompareExchange(ref location, 0, 0);
#endif

它们的原子性也记录

Volatile 类还提供一些 64 位类型的读写操作,例如 Int64Double。与常规读取和写入不同,即使在 32 位处理器上,此类 64 位内存上的易失性读取和写入也是原子的。

不过,使用这两种方法并不像 volatile 关键字那么方便。需要注意的是,不要忘记将 volatile 字段的每个读/写访问分别包装在 Volatile.ReadVolatile.Write 中。

Starting from .NET Framework 4.5, it is now possible to perform a volatile read or write on long or double variables by using the Volatile.Read and Volatile.Write methods. These methods perform atomic reads and writes on the long/double variables, as it's evident from their implementation:

private struct VolatileIntPtr { public volatile IntPtr Value; }

[Intrinsic]
[NonVersionable]
public static long Read(ref long location) =>
#if TARGET_64BIT
    (long)Unsafe.As<long, VolatileIntPtr>(ref location).Value;
#else
    // On 32-bit machines, we use Interlocked,
    // since an ordinary volatile read would not be atomic.
    Interlocked.CompareExchange(ref location, 0, 0);
#endif

Their atomicity is documented too:

The Volatile class also provides read and write operations for some 64-bit types such as Int64 and Double. Volatile reads and writes on such 64-bit memory are atomic even on 32-bit processors, unlike regular reads and writes.

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 in Volatile.Read or Volatile.Write respectively.

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文