【问题标题】:volatile for reference type in .net 4.0.net 4.0 中引用类型的 volatile
【发布时间】:2012-01-29 13:13:51
【问题描述】:

我对 volatile 的引用类型感到困惑。

我知道对于原始类型,volatile 可以立即反映来自另一个线程的值更改。对于引用类型,它可以立即反映地址的变化。但是,对象的内容呢?它们是否仍被缓存?

(假设List.Add()是原子操作)

例如,我有:

class A
{
     volatile List<String> list;
     void AddValue()
     {
        list.Add("a value");
     }

}

如果一个线程调用函数AddValue,列表的地址没有改变,另一个线程是否会更新列表的“内容”变化,或者内容可能为每个线程缓存而不是更新其他线程?

【问题讨论】:

  • 我不确定这一点,但我的猜测是,鉴于该列表实际上只是一个指针(地址),当内容更新时,它会立即反映在任何地方,因为内容将驻留在堆,然而,随着地址的改变,堆栈被改变了,我认为它可以驻留在一个核心上。但是,如前所述,没有专家。这只是一个猜测。
  • 另外,作为旁注。大量优化的易失性转变。很多优化。有些人强烈建议不要使用 volatile 修饰符,而是在需要时使用 volatileread 和 volatilewrite(在系统命名空间中的类上找到的一些全局静态方法,可能在 google 上可以找到)。
  • @Alxandr:小心点。易失性读取和易失性写入是调用的潜在昂贵方法,它们会引发全栅栏,而不是人们期望的半栅栏。如果您出于性能原因编写无锁代码(以及您必须做这件疯狂的事情的其他原因是什么?),那么 VolatileRead 和 VolatileWrite 完全有可能更糟。你必须仔细分析它。
  • volatile 修饰符在 all 上诱导 volatile 读写,是的 ALL,对字段进行读写,但使用 volatile 读写是可选的。此外,afaik,易失性读写总是比锁定快至少 10 倍,而且它不会以任何方式做同样的事情。至少那是我关于 .NET 线程的视频告诉我的(我认为是在 lidnug 上找到的)。
  • 现在去找一个视频来解释你不同意/争论的人...... :)

标签: c# .net volatile


【解决方案1】:

查看http://www.albahari.com/threading/part4.aspx#_The_volatile_keyword 以获得关于 volatile 实际作用以及它如何影响字段的良好解释。

无论如何,该网站上线程的整个部分都是必读的,它包含大量有用的信息,在我设计多线程软件时证明对我非常有用。

【讨论】:

    【解决方案2】:

    我知道对于原始类型,volatile可以立即反映另一个线程的值变化

    您至少在三个方面理解错误。在您深入了解弱内存模型、获取和释放语义以及它们如何影响您的程序的一切之前,您不应该尝试使用 volatile。

    首先,请明确 volatile 影响变量,而不是

    其次,volatile 对包含值类型值的变量的影响与它对包含引用的变量的影响没有任何不同。

    第三,易失性并不意味着来自其他线程的值更改立即可见。 Volatile 意味着变量具有获取和释放语义。易失性影响在特定线程中可以观察到内存突变的副作用发生的顺序。存在一致的通用突变顺序并且可以从所有线程中立即观察到该顺序的突变的想法并不是内存模型做出的保证。

    但是,对象的内容呢?

    怎么样?引用类型的 volatile 变量所引用的存储位置不需要具有任何特定的线程特性。

    如果一个线程调用函数AddValue,列表的地址没有改变,另一个线程是否会更新列表的“内容”变化。

    不。为什么会呢?那个其他线程可能在不同的处理器上,并且那个处理器缓存可能已经预加载了包含支持列表的数组地址的页面。改变列表可能会改变包含数组地址的存储位置,以引用一些完全不同的位置。

    当然,列表类一开始就不是线程安全的。如果您没有锁定对列表的访问权限,那么当您尝试执行此操作时,列表可能会崩溃并死掉。

    你不需要 volatile;您需要在对列表的访问周围放置线程锁。由于线程锁会产生完整的栅栏,因此您不需要 volatile 引入的半栅栏。

    【讨论】:

    • 非常感谢埃里克。根据我对您的解释的理解, volatile 用于防止内存突变的副作用。但是,什么是用来防止CPU寄存器复制内存值的。在多核 CPU 中,两个核很多缓存了变量的两个副本(例如一个 int),如果其中一个更新它,另一个将如何获取它。我们是否需要在 C# 代码中执行任何操作,还是由 .net 自动处理?再来一次。
    • @Feng:你误会我了。我并没有说“易失性”可以防止记忆突变的副作用;它到底是怎么做到的?相反,“易失性”会影响可能观察发生这些突变的顺序。它通过将获取和释放语义引入变量读取和写入来实现。 如果您不了解获取和释放语义的工作原理,那么您不应该使用 volatile。 当然,这样做也会关闭缓存优化。
    【解决方案3】:

    volatile 关键字对列表的内容(或更准确地说,被引用的对象)没有影响。

    为另一个线程谈论 更新 / 未更新 是对正在发生的事情的过度简化。您应该使用lock 语句来同步对共享列表的访问。否则,您实际上将面临可能导致程序崩溃的竞争条件。 List&lt;T&gt; 类本身不是线程安全的。

    【讨论】:

      【解决方案4】:

      比那更糟。

      如果您同时访问一个不是线程安全的对象,您的程序实际上可能会崩溃。获取过时的信息并不是最坏的潜在结果。

      在线程之间共享 .NET 基类库对象时,您真的别无选择,只能使用锁定。对于无锁编程,您需要在最低级别对数据结构进行侵入性更改。

      【讨论】:

        猜你喜欢
        • 2010-10-30
        • 2019-05-18
        • 2011-01-25
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2012-07-20
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多