与使用 Interlocked 类相比,使用 volatile 关键字有什么优势吗?

发布于 2024-08-10 01:00:20 字数 54 浏览 4 评论 0原文

换句话说,我可以用一个易失性变量做一些普通变量和 Interlocked 类无法解决的事情吗?

In other words, can I do something with a volatile variable that could not also be solved with a normal variable and the Interlocked class?

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

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

发布评论

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

评论(5

〃温暖了心ぐ 2024-08-17 01:00:20

编辑:问题很大程度上重写了

为了回答这个问题,我进一步深入研究了这个问题,发现了一些关于易失性互锁的事情我不知道。让我们澄清这一点,不仅是为了我,也是为了这次讨论和其他阅读此内容的人:

  • 易失性读/写应该不受重新排序的影响。这意味着读取和写入,它意味着任何其他操作;
  • 波动性不是在CPU上强制的,即硬件级别(x86在任何读/写上使用获取和释放栅栏)。它确实会阻止编译器或 CLR 优化;
  • Interlocked 使用原子汇编指令进行 CompareExchange (cmpxchg)、Increment (inc) 等;
  • 互锁 有时确实使用锁:a 多处理器系统上的硬件锁定;在单处理器系统中,没有硬件锁;
  • Interlocked易失性 的不同之处在于它使用全栅栏,而易失性使用半栅栏。
  • 当您使用易失性时,写入后的读取可以重新排序代码>. Interlocked 不会发生这种情况。 VolatileReadVolatileWrite 具有与“易失性”相同的重新排序问题(感谢 Brian Gideon 提供的链接)。

现在我们已经有了规则,我们可以定义您的问题的答案:

  • 从技术上讲:是的,有些事情您可以使用 volatile 完成,而无法使用 Interlocked 完成:
    1. 语法:您不能在 ab 为易失性的情况下编写 a = b,但这很明显;
    2. 由于重新排序,您可以在将其写入易失性变量后读取不同的值。您无法使用Interlocked 来执行此操作。换句话说:使用易失性可能不太安全,然后使用Interlocked
    3. 性能:易失性互锁 更快。​​
  • 语义上:不,因为 Interlocked 只是提供操作的超集,并且使用起来更安全,因为它应用了完整的防护。您无法使用 volatile 做任何使用 Interlocked 做不到的事情,并且可以使用 Interlocked很多你不能用 volatile 做到这一点:

    静态易失性 int x = 0;
    x++; // 非原子的
    静态 int y = 0;
    互锁。增量(y); // 原子
    
  • 作用域:是的,声明一个变量 易失性 使其对于每次访问都是易失性的。不可能以任何其他方式强制执行此行为,因此 volatile 不能替换为 Interlocked。在其他库、接口或硬件可以访问您的变量并随时更新它,或者需要最新版本的情况下,这是需要的。

如果您问我,最后一点是对 易失性 的实际需求,并且可能使其成为两个进程共享内存并且需要在不锁定的情况下读取或写入的理想选择。在这种情况下,将变量声明为易失性比强制所有程序员使用Interlocked(编译器无法强制执行)要安全得多。


编辑:以下引用是我原始答案的一部分,我将其保留在 ;-)

来自 C# 编程语言 标准的引用:

对于非易失性字段,优化
考虑重新排序的技术
指令可能会导致意外的结果
和不可预测的结果
访问的多线程程序
没有同步的字段,例如
lock-statement提供。
这些优化可以通过以下方式执行
编译器,通过运行时系统,
或通过硬件。对于易变字段,
这种重新排序优化是
限制:

  • 对易失性字段的读取称为易失性读取。易失性读取
    具有:获取语义”;也就是说,它
    保证发生在任何之前
    对之后发生的内存的引用
    它在指令序列中。

  • 对易失性字段的写入称为易失性写入。一个
    易失性写有“释放
    语义”;也就是说,它被保证
    在任何内存引用之后发生
    在写指令之前
    指令顺序。

更新:问题大部分被重写,更正了我原来的回答并添加了“真实”答案

EDIT: question largely rewritten

To answer this question, I dived a bit further in the matter and found out a few things about volatile and Interlocked that I wasn't aware of. Let's clear that out, not only for me, but for this discussion and other people reading up on this:

  • volatile read/write are supposed to be immune to reordering. This only means reading and writing, it does not mean any other action;
  • volatility is not forced on the CPU, i.e., hardware level (x86 uses acquire and release fences on any read/write). It does prevent compiler or CLR optimizations;
  • Interlocked uses atomic assembly instructions for CompareExchange (cmpxchg), Increment (inc) etc;
  • Interlocked does use a lock sometimes: a hardware lock on multi processor systems; in uni-processor systems, there is no hardware lock;
  • Interlocked is different from volatile in that it uses a full fence, where volatile uses a half fence.
  • A read following a write can be reordered when you use volatile. It can't happen with Interlocked. VolatileRead and VolatileWrite have the same reordering issue as `volatile (link thanks to Brian Gideon).

Now that we have the rules, we can define an answer to your question:

  • Technically: yes, there are things you can do with volatile that you cannot do with Interlocked:
    1. Syntax: you cannot write a = b where a or b is volatile, but this is obvious;
    2. You can read a different value after you write it to a volatile variable because of reordering. You cannot do this with Interlocked. In other words: you can be less safe with volatile then you can be with Interlocked.
    3. Performance: volatile is faster then Interlocked.
  • Semantically: no, because Interlocked simply provides a superset of operations and is safer to use because it applies full fencing. You can't do anything with volatile that you cannot do with Interlocked and you can do a lot with Interlocked that you cannot do with volatile:

    static volatile int x = 0;
    x++;                        // non-atomic
    static int y = 0;
    Interlocked.Increment(y);   // atomic
    
  • Scope: yes, declaring a variable volatile makes it volatile for every single access. It is impossible to force this behavior any other way, hence volatile cannot be replaced with Interlocked. This is needed in scenarios where other libraries, interfaces or hardware can access your variable and update it anytime, or need the most recent version.

If you'd ask me, this last bit is the actual real need for volatile and may make it ideal where two processes share memory and need to read or write without locking. Declaring a variable as volatile is much safer in this context then forcing all programmers to use Interlocked (which you cannot force by the compiler).


EDIT: The following quote was part of my original answer, I'll leave it in ;-)

A quote from the the C# Programming Language standard:

For nonvolatile fields,optimization
techniques that consider that reorder
instructions can lead to unexpected
and unpredictable results in
multithreaded programs that access
fields without synchronization such as
that provided by the lock-statement.
These optimizationscan be performed by
the compiler, by the runtime system,
or by hardware. For volatile fields,
such reordering optimizations are
restricted:

  • A read of a volatile field is called a volatile read. A volatile read
    has :acquire semantics"; that is, it
    is guaranteed to occur prior to any
    references to memory that occur after
    it in the instruction sequence.

  • A write of a volatile field is called a volatile write. A
    volatile write has "release
    semantics"; that is, it is guaranteed
    to happen after any memory references
    prior to the write instruction in the
    instruction sequence.

Update: question largely rewritten, corrected my original response and added a "real" answer

漫雪独思 2024-08-17 01:00:20

这是一个相当复杂的话题。我发现 Joseph Albahari 的文章是更明确和准确的来源之一.NET Framework 中的多线程概念可能有助于回答您的问题。

但是,快速总结一下,就如何使用而言,volatile 关键字和 Interlocked 类之间存在很多重叠。当然,两者都远远超出了普通变量的范围。

This is a fairly complex topic. I find Joseph Albahari's writeup to be one of the more definitive and accurate sources for multithreading concepts in the .NET Framework that might help answer your question.

But, to quickly summarizes there is a lot of overlap between the volatile keyword and the Interlocked class as far as how they can be used. And of course both go way above and beyond what you can do with a normal variable.

清风无影 2024-08-17 01:00:20

是的 - 您可以直接查看该值。

只要您仅使用 Interlocked 类来访问变量,那么就没有区别。 易失性的作用是告诉编译器该变量是特殊的,并且在优化时不应该假设该值没有改变。

采用此循环:

bool done = false;

...

while(!done)
{
... do stuff which the compiler can prove doesn't touch done...
}

如果您在另一个线程中将 done 设置为 true,您将期望循环退出。然而,如果done没有被标记为易失性,那么编译器可以选择意识到循环代码永远不会改变done,并且它可以优化退出的比较。

这是多线程编程的困难之一——许多问题只在某些情况下才会出现。

Yes - you can look at the value directly.

As long as you ONLY use the Interlocked class to access the variable then there is no difference. What volatile does is it tells the compiler that the variable is special and when optimizing it shouldn't assume that the value hasn't changed.

Takes this loop:

bool done = false;

...

while(!done)
{
... do stuff which the compiler can prove doesn't touch done...
}

If you set done to true in another thread you would expect the loop to exit. However - if done is not marked as volatile then the compiler has the option to realize that the loop code can never change done and it can optimize out the compare for exit.

This is one of the difficult things about multithread programming - many of the situations which are problems only come up in certain situations.

关于从前 2024-08-17 01:00:20

我不会试图成为这个主题的权威,但我强烈建议您看一下 这篇文章,作者是大名鼎鼎的乔恩·斯基特 (Jon Skeet)。

另请查看此答案的最后部分,其中详细介绍了哪些内容应该使用易失性

I won't attempt to be an authority on this subject but I would highly recommend that you take a look at this article by the vaunted Jon Skeet.

Also take a look at the final part of this answer which details what volatile should be used for.

無處可尋 2024-08-17 01:00:20

是的,您可以通过使用易失性变量而不是锁来获得一些性能。

锁是一个完整的内存屏障,它可以为您提供与易失性变量以及许多其他变量相同的特征。正如已经说过的,易失性只是确保在多线程场景中,如果一个 CPU 更改其缓存行中的值,其他 CPU 会立即看到该值,但根本不确保任何锁定语义。

问题是锁比 易失性 更强大,你应该尽可能使用易失性以避免不必要的锁。

Yes, you can gain some performance by using a volatile variable instead of a lock.

Lock is a full memory barrier which can give you the same characteristics of a volatile variable as well as many others. As has already been said volatile just ensures that in multi-threaded scenarios if a CPU changes a value in its cache line, the other CPUs sees the value immediately but do not ensure any locking semantic at all.

The thing is lock is a lot more powerful than volatile and you should use volatile when you can to avoid unnecessary locks.

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