【问题标题】:Use of volatile (Thread.VolatileRead/ Thread.VolatileWrite) in C#在 C# 中使用 volatile (Thread.VolatileRead/ Thread.VolatileWrite)
【发布时间】:2008-12-08 11:07:15
【问题描述】:

在多 CPU 机器上运行的多线程程序中,我是否需要使用易失性读/写访问共享状态(下面示例代码中的 _data)以确保正确性。

也就是说堆对象可以缓存在cpu上吗?

使用下面的示例并假设多线程将访问 GetValue 和 Add 方法,我需要 ThreadA 能够添加数据(使用 Add 方法)和 ThreadB 能够立即查看/获取添加的数据(使用GetValue 方法)。那么我是否需要向 _data 添加易失性读/写来确保这一点?基本上我不想在 ThreadA 的 cpu 上添加要缓存的数据。

/我没有锁定(强制执行独占线程访问),因为代码需要超快,并且我没有从 _data 中删除任何数据,所以我不需要锁定 _data。

谢谢。

**** 更新 ****************************

显然,你们认为使用这个例子来实现无锁是个坏主意。但是我在这里会面临哪些副作用或例外情况?

如果 1 个线程正在迭代读取值而另一个线程正在迭代更新值,那么 Dictionary 类型会抛出异常吗?还是我只会遇到“脏读”(在我的情况下这很好)?

**** 结束更新 ****************************

public sealed class Data
{
    private volatile readonly Dictionary<string, double> _data = new Dictionary<string, double>();

    public double GetVaule(string key)
    {
        double value;
        if (!_data.TryGetValue(key, out value))
        {
            throw new ArgumentException(string.Format("Key {0} does not exist.", key));
        }
        return value;
    }

    public void Add(string key, double value)
    {
        _data.Add(key, value);
    }

    public void Clear()
    {
        _data.Clear();
    }
}

感谢您的回复。关于锁,这些方法几乎经常被多个线程调用,所以我的问题是有争议的锁而不是实际的锁操作。

所以我的问题是关于 cpu 缓存,堆对象(_data 实例字段)可以缓存在 cpu 上吗?我是否需要使用易失性读/写访问 _data 字段?

/另外,我被 .Net 2.0 困住了。

感谢您的帮助。

【问题讨论】:

    标签: c# multithreading


    【解决方案1】:

    Dictionary&lt;TKey, TValue&gt;MSDN docs 说它对多个读者 是安全的,但他们并没有像其他一些类那样提供“一个作者,多个读者”的保证。简而言之,我不会这样做。

    您说您避免锁定是因为您需要“超快”代码 - 您是否尝试过锁定以查看开销是多少?无争议的锁非常便宜,当锁有争议时,您就可以从增加的安全性中受益。在决定担心无锁解决方案的并发问题之前,我肯定会对此进行广泛的分析。如果您实际上有多个阅读器,ReaderWriterLockSlim 可能会很有用,但听起来您至少目前只有一个阅读器和一个作者 - 在这种情况下,简单的锁定会更容易。

    【讨论】:

      【解决方案2】:

      我认为您可能误解了volatile 关键字的使用(无论是那个还是我,请随时纠正我)。 volatile 关键字保证来自多个线程的变量本身的值的获取和设置操作将始终处理相同的副本。例如,如果我有一个指示状态的bool,那么在一个线程中设置它会使新值立即可供另一个线程使用。

      但是,您永远不会更改变量的值(在本例中为引用)。您所做的就是操作引用指向的内存区域。将其声明为volatile readonly(如果我的理解是正确的,它会通过不允许设置它来破坏 volatile 的目的)不会对正在操作的实际数据(@ 的后端存储)产生任何影响987654325@)。

      话虽如此,在这种情况下您确实需要使用锁。您的危险超出了“脏读”(意味着您所读的内容在某些时候是有效的)的前景,进入了真正未知的领域。正如 Jon 所说,在您尝试走无锁编码之路之前,您确实需要证明锁定会产生不可接受的性能。否则,这就是过早优化的缩影。

      【讨论】:

        【解决方案3】:

        问题在于你的 add 方法:

        public void Add(string key, double value)
        {
            _data.Add(key, value);
        }
        

        可能会导致 _data 决定完全重新组织它所持有的数据 - 此时 GetVaule 请求可能会以任何可能的方式失败。

        您需要一个锁或不同的数据结构/数据结构实现。

        【讨论】:

          【解决方案4】:

          如果您开始在其上调用方法,我认为 volatile 不能替代锁定。您保证线程 A 和线程 B 看到字典的相同副本,但您仍然可以同时访问字典。您可以使用多模式锁来增加并发性。例如,请参阅ReaderWriterLockSlim

          表示一个锁,用于 管理对资源的访问,允许 多个线程用于阅读或 专有权写作。

          【讨论】:

            【解决方案5】:

            volatile 关键字与锁定无关,它用于指示指定字段的值可能被不同的线程或其他可以与您的代码同时运行的东西更改或读取。这对于编译器来说至关重要,因为许多优化过程都涉及缓存变量值和重新排列指令。 volatile 关键字将告诉编译器在优化那些引用 volatile 变量的指令时要“谨慎”。

            对于字典的多线程使用,有很多方法可以做。最简单的方法是使用lock 关键字,它具有足够的性能。如果您需要更高的性能,您可能需要为您的特定任务实现自己的字典。

            【讨论】:

              【解决方案6】:

              Volatile 没有锁定,它与同步无关。对只读数据进行无锁读取通常是安全的。请注意,仅仅因为您没有从 _data 中删除任何内容,您似乎调用了 _data.Add()。那不是只读的。所以是的,这段代码会以各种令人兴奋且难以预测的方式在你面前爆炸。

              使用锁,很简单,更安全。如果您是无锁大师(您不是!),并且分析显示了与锁争用相关的瓶颈,并且您无法通过分区或切换到自旋锁来解决争用问题,那么只有这样您才能研究获得无锁读取的解决方案,这将涉及从头开始编写自己的字典,并且可能比锁定解决方案更快。

              您是否开始看到您的想法离我们有多远?只需使用该死的锁!

              【讨论】:

                猜你喜欢
                • 1970-01-01
                • 2018-01-08
                • 1970-01-01
                • 1970-01-01
                • 2014-12-07
                • 1970-01-01
                • 2020-04-01
                • 1970-01-01
                相关资源
                最近更新 更多