为什么只读和易失性修饰符是互斥的?

8
我有一个引用类型的变量,它是只读的,因为引用从不改变,只有它的属性会改变。当我尝试给它添加volatile修饰符时,编译器警告我不能让两个修饰符应用于同一个变量。但我认为我需要它是volatile的,因为我不想在读取其属性时出现缓存问题。我错过了什么吗?还是编译器错了?
更新:如Martin在下面的评论中所述:对于引用类型对象,只读和volatile修饰符仅适用于引用,而不适用于对象的属性。这就是我错过的,所以编译器是正确的。
class C
{
    readonly volatile string s;  // error CS0678: 'C.s': a field cannot be both volatile and readonly
}

编译器可能存在问题(尽管对于您的特定情况可能并非如此)。 - Douglas
3个回答

16

readonlyvolatile 关键字都不是具有渗透性的,它们应用于引用本身而非对象的属性。

readonly 关键字断言并强制执行一个变量在初始化后不能改变。该变量是存储引用的小块内存。

volatile 关键字告诉编译器,一个变量的内容可能会被多个线程更改。这可以防止编译器使用优化(例如将变量的值读入寄存器并在几个指令中使用该值),这可能会导致并发访问问题。同样,这只会影响存储引用的小块内存。

以这种方式应用,可以看到它们确实是互斥的。如果某物是只读的(只能在初始化或构造时写入一次),则不能同时是易变的(可以由多个线程随时写入)。


至于您对缓存问题的担忧,如果我没记错的话,编译器可以缓存属性调用结果的时间有相当严格的规定。请记住它是一个方法调用,并且它是一个相当重的优化(从编译器的角度来看)可以缓存它的值并跳过再次调用它。我认为您不需要过分担心这个问题。


1
这些属性可能是不可缓存的(我不确定),但是支持它们的字段绝对是。这些字段应该被标记为volatile。 - configurator
这个答案正确地回答了OP的问题,但它包含了一个误导性的陈述:“volatile关键字告诉编译器变量的内容可能会被多个线程更改。” 这只是部分正确的;volatile关键字还告诉编译器变量的内容可能会被多个线程同时更改或读取,即使更改只执行一次。我在这里提出了一个类似的问题,涵盖了这个问题:C#语言缺陷:volatile和readonly不应该互斥 - Douglas
我不认为我的陈述是误导性的。也许更准确的说法是,它告诉编译器一个变量的值在一个线程读取它时可能会被其他线程改变,但我认为那个措辞更具有误导性,如果没有进一步的解释,它可能会暗示volatile就足以提供线程同步。我认为我的措辞清楚地解释了这个概念,下一句话提供了足够的细节来理解基本概念。你的问题是.NET初始化行为的有趣研究,但这并不影响这个问题。 - P Daddy

1

只读字段只能在对象首次构造时写入。因此,由于该字段是不可变的且不可能更改,CPU上不会出现任何缓存问题。


我理解你的逻辑,但我不同意。一个对象可以是只读的,但仍然可以通过其属性进行更改,因为它的属性并非只读。在这种情况下,只读属性只是防止将变量分配给另一个对象。至少这是我从所学到的内容中理解的。 - Jader Dias
2
@Vernict:readonly和volatile修饰符仅保护引用(或原子值如bool、int的值),而不是对象的内容!这是完全不同的问题。 - Martin C.

0

虽然引用本身可能是线程安全的,但它的属性可能不是。想象一下,如果两个线程尝试同时迭代包含在您的引用对象中的列表会发生什么。


但是,只读和易失性都不打算保护这一点。这是您需要通过同步来解决的问题。 - Martin C.
@Charlie,你的意思是即使对象是volatile类型,也不能保证它的属性会是volatile? - Jader Dias
@Martin C,非常正确。@Vernicht,据我所知,是的。您需要确保您的字段和属性是线程安全的。 - Charlie Salts

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