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

C# 为什么只读和易失性修饰符是互斥的?,c#,.net,multithreading,readonly,volatile,C#,.net,Multithreading,Readonly,Volatile,我有一个引用类型变量,它是只读的,因为引用永远不会改变,只会改变它的属性。当我尝试向它添加volatile修饰符时,编译后的程序警告我,它不会让两个修饰符应用于同一个变量。但我认为我需要它是易失性的,因为我不想在读取它的属性时出现缓存问题。我遗漏了什么吗?还是编译器错了 更新如Martin在下面的一条评论中所述:对于引用类型的对象,只读和易失性修改器仅适用于引用,而不适用于对象的属性。这就是我所缺少的,因此编译器是正确的 class C { readonly volatile strin

我有一个引用类型变量,它是
只读的
,因为引用永远不会改变,只会改变它的属性。当我尝试向它添加
volatile
修饰符时,编译后的程序警告我,它不会让两个修饰符应用于同一个变量。但我认为我需要它是易失性的,因为我不想在读取它的属性时出现缓存问题。我遗漏了什么吗?还是编译器错了

更新如Martin在下面的一条评论中所述:对于引用类型的对象,只读和易失性修改器仅适用于引用,而不适用于对象的属性。这就是我所缺少的,因此编译器是正确的

class C
{
    readonly volatile string s;  // error CS0678: 'C.s': a field cannot be both volatile and readonly
}

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

虽然引用本身可能是线程安全的,但其属性可能不是。想一想如果两个线程试图同时遍历引用对象中包含的列表会发生什么情况。

无论是
只读
还是
易失性
修饰符都不是穿透性的。它们应用于引用本身,而不是对象的属性

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

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

通过这种方式应用,您可以看到它们确实是相互排斥的。如果某个东西是只读的(只能在初始化或构造时写入一次),那么它也不能是易失性的(可以在任何时候由多个线程写入)



关于缓存问题,IIRC,编译器何时可以缓存属性调用的结果有非常严格的规则。请记住,是一个方法调用,缓存它的值并跳过再次调用它是一个非常繁重的优化(从编译器的角度来看)。我不认为这是你需要过分担心的事情。

我理解你的逻辑,但我不同意。对象可以是只读的,并且仍然可以通过其属性进行更改,因为其属性不是只读的。本例中的readonly属性仅阻止将变量分配给另一个对象。至少这是我从中学到的。@Vernict:readonly和volatile修饰符只保护引用(或原子值如bool、int时的值),而不是对象的内容!这是一个完全不同的问题,但只读和易失性都不是用来保护的。不管怎样,这都是同步需要解决的问题。@Charlie,你是说即使对象是易变的,也不能保证它的属性是易变的?@Martin C,非常正确@据我所知,是的。您需要保证您的字段和属性是线程安全的。这些属性可能是不可缓存的(我不知道),但支持字段肯定是。字段应该标记为volatile。这个答案正确地回答了OP的问题,但它包含一个误导性的陈述:“volatile关键字告诉编译器一个变量的内容可能会被多个线程更改。”这只是部分正确;volatile关键字还告诉编译器,一个变量的内容可能会被多个线程同时更改或读取,即使更改只执行一次。我在这里提出了一个关于这个问题的类似问题:。我不认为我的陈述有误导性。也许更准确的说法是,它告诉编译器,当线程读取变量时,其他线程可能会更改变量的值,但我认为,如果不作进一步解释,措辞更容易误导,因为这可能意味着
volatile
是提供线程同步所必需的全部。我认为我的措辞清楚地解释了这个概念,下一句话提供了足够的细节,可以让你有一个基本的理解。您的问题是对.NET的初始化行为进行有趣的检查,但这并没有影响到这一点。