【问题标题】:Why readonly and volatile modifiers are mutually exclusive?为什么 readonly 和 volatile 修饰符是互斥的?
【发布时间】:2008-12-28 16:55:16
【问题描述】:

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

更新 正如 Martin 在以下 cmets 之一中所述:对于引用类型的对象,readonly 和 volatile 修饰符都仅适用于引用,而不适用于对象的属性。这就是我所缺少的,所以编译器是对的。

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

【问题讨论】:

标签: c# .net multithreading readonly volatile


【解决方案1】:

readonlyvolatile 修饰符都不是穿透性的。它们适用于引用本身,而不是对象的属性。

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

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

这样应用,你可以看到它们确实是互斥的。如果某些东西是只读的(只能在初始化或构造时写入一次),那么它也不能是易失的(可以随时被多个线程写入)。


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

【讨论】:

  • 属性可能是不可缓存的(我不知道),但支持字段肯定是。这些字段应标记为 volatile。
  • 这个答案正确地解决了 OP 的问题,但它包含一个误导性的陈述:“volatile 关键字告诉编译器变量的内容可能会被多个线程更改。”这只是部分正确; volatile 关键字还告诉编译器变量的内容可能会被多个线程同时更改或读取,即使更改只执行一次。我在这里提出了一个涵盖此问题的类似问题:C# language flaw: volatile and readonly should not be mutually exclusive
  • 我不认为我的陈述具有误导性。也许更准确地说,它告诉编译器一个变量的值可能会在一个线程正在读取它时被其他线程更改,但我认为 措辞更容易误导,无需进一步解释,因为它可能暗示volatile 是提供线程同步所必需的。我认为我的措辞清楚地解释了这个概念,下一句为基本理解提供了足够的细节。您的问题是对 .NET 初始化行为的有趣检查,但它并没有减损这一点。
【解决方案2】:

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

【讨论】:

  • 我理解你的逻辑,但我不同意。一个对象可以是只读的,并且仍然可以通过其属性进行更改,因为它的属性不是只读的。在这种情况下,只读属性仅防止将变量分配给另一个对象。至少这是我从所学中所理解的。
  • @Vernict:readonly 和 volatile 修饰符仅保护引用(或在 bool、int 等原子值的情况下的值),而不是对象的内容!那是完全不同的事情。
【解决方案3】:

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

【讨论】:

  • 但这既不是只读也不是 volatile 旨在防止的。无论如何,这都是您需要通过同步来解决的问题。
  • @Charlie,你是说即使对象是 volatile 的,也不保证它的属性是 volatile 的?
  • @Martin C,非常正确。 @Vernicht,据我了解,是的。您需要保证您的字段和属性是线程安全的。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2011-08-26
  • 1970-01-01
  • 2012-07-21
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多