对一个易失域的引用不会被视为易失的,其含义是什么?

44

以下代码

using System.Threading;

class Test
{
    volatile int counter = 0;
    public void Increment()
    {
        Interlocked.Increment(ref counter);
    }
}

引发以下编译器警告:

"A reference to a volatile field will not be treated as volatile"

我在这里做错了什么导致出现了这个警告?为什么编译器要警告我?

4个回答

47

您没有做错任何事情。根据文档

通常不应使用ref或out参数传递易失性字段,因为在函数范围内它将不被视为易失性。当调用交错API等异常情况时,有例外。


20
我认为Interlocked方法对待它的方式与它们对待任何其他字段的方式没有任何区别 - 只是Interlocked以一种特殊的方式处理所有字段。 - Jon Skeet
3
同意,"Volatile" 对于 Interlocked API 是无关紧要的。但在某些情况下,将 Interlocked 和 volatile 混合使用非常有用。 - IamIC
有点晚了,但 @IamIC:能详细说明是哪些情况吗? - jajdoo
1
简而言之,volatile仅仅是阻止编译器和CPU对值进行优化,以避免线程/核心无法看到最新的值。因此,对于从多个线程访问的值,它是一个强制属性。但是,volatile不提供原子操作。为此,我们需要Interlocked。我们可以只使用Interlocked而不是volatile,这就是.Net正在寻找的。但在x86上,这对于读取来说效率较低,因此混合使用两者是最佳选择。在Itanium和ARM上,我们必须始终使用Interlocked。 - IamIC

35

基本上,警告是当你通过引用传递一个易失性字段时,调用代码不知道如何以易失性的方式对待它。对于Interlocked.Increment来说,由于该方法的特性,这可能并不重要——但是,如果您使用Interlocked,您就不需要将变量设置为易失性的anyway

总的来说,我认为应该避免混合使用这两种方式——如果你正在使用Interlocked,在everywhere都要使用(使用Interlocked.CompareExchange(ref counter, 0, 0)进行读取)。我个人很少使用易失性,对于简单的计数器,我might会使用Interlocked,但对于大多数任务,我更倾向于使用锁。


1
一个问题,你会不会访问你回答的每个问题来查看是否有评论,或者你有一种RSS提醒机制? - Jader Dias
3
Interlocked做了一切必要的事情来确保正确性——包括内存屏障、原子性等。如果没有这些,它就毫无用处。 - Jon Skeet
@JonSkeet 我的意思是Interlocked旨在确保操作是原子性的。Volatile旨在确保变量不被缓存且是“新鲜的”。虽然Interlocked也可以做到这一点,但它们的目的不同。 - IamIC
1
@IanC:Interlocked.Read和Interlocked.MemoryBarrier怎么样?我还会认为,volatile的确切保证行为足够微妙,大多数开发人员不应该使用它(包括我自己)。 - Jon Skeet
1
@JonSkeet 我同意volatile是不稳定的 :). 它不能普遍应用于64位变量,这一点几乎足以忽略它,我个人只将其用于32位变量和对象的新鲜度,并且理解这仅适用于应用volatile的变量。就我而言,它没有其他保证,比如防止写入/读取重排序(参见Albahari)。为此,使用Interlocked / MemoryBarrier / lock。 Interlocked.Read和Thread.VolatileRead也可用于保证新鲜度,就像volatile一样。在这方面,我理解你的意思。 - IamIC
显示剩余5条评论

31

使用这个:

#pragma warning disable 420
if(Interlocked.CompareExchange(ref isLoaded, 1, 0) != 0)
    return;
#pragma warning restore 420

4
一旦你理解了其他答案,就会发现它非常有用,如果你决定同时使用Interlocked和volatile的话。 - yoyo
5
不,但它告诉你如何处理警告信息。这非常有用。此外,艺术作品也有点有趣。 - BrainSlugs83
@RyanM,那幅艺术作品很有趣。 为什么你把它移除了?没有它,这个回答看起来有些空洞啊! - Theodor Zoulias

3

你之所以会出现这个错误,是因为你正在通过引用传递该字段。我认为这意味着目标方法不知道该字段被标记为volatile,因此不会将其视为这样处理。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接