c# - volatile 关键字用法与锁

标签 c# multithreading performance dispatcher volatile

我在不确定是否有必要的地方使用了 volatile。我很确定在我的情况下锁会有点矫枉过正。阅读此主题(Eric Lippert 评论)让我对 volatile 的使用感到焦虑:When should the volatile keyword be used in c# ?

我使用 volatile 是因为我的变量在多线程上下文中使用,在该上下文中可以同时访问/修改该变量,但我可以在其中松散添加而不会造成任何伤害(参见代码)。

我添加了“volatile”以确保不会发生对齐错误:仅读取变量的 32 位和其他 32 位在另一个提取中,这可以通过另一个线程在中间的写入在 2 中中断。

我之前的假设(之前的陈述)真的会发生吗?如果不是,是否仍然需要使用“volatile”(选项属性修改可能发生在任何线程中)。

After reading the 2 first answers. I would like to insists on the fact that the way the code is written, it is not important if due to concurrency we miss an increment (want to increment from 2 threads but the result is only incremented by one due to concurrency) if at least the variable '_actualVersion' is incremented.

作为引用,这是我正在使用的代码部分。它是仅在应用程序空闲时报告保存操作(写入磁盘)。

public abstract class OptionsBase : NotifyPropertyChangedBase
{
    private string _path;

    volatile private int _savedVersion = 0;
    volatile private int _actualVersion = 0;

    // ******************************************************************
    void OptionsBase_PropertyChanged(object sender, System.ComponentModel.PropertyChangedEventArgs e)
    {
        _actualVersion++;
        Application.Current.Dispatcher.BeginInvoke(new Action(InternalSave), DispatcherPriority.ApplicationIdle);
    }

    // ******************************************************************
    private void InternalSave()
    {
        if (_actualVersion != _savedVersion)
        {
            _savedVersion = _actualVersion;
            Save();
        }
    }

    // ******************************************************************
    /// <summary>
    /// Save Options
    /// </summary>
    private void Save()
    {
        using (XmlTextWriter writer = new XmlTextWriter(_path, null))
        {
            writer.Formatting = Formatting.Indented;
            XmlSerializer x = new XmlSerializer(this.GetType());

            x.Serialize(writer, this);
            writer.Close();
        }
    }

最佳答案

I've used volatile where I'm not sure it is necessary.

让我非常清楚这一点:

如果您不是 100% 清楚 volatile 在 C# 中的含义,那么不要使用它它是一个仅供专家使用的利器.如果您无法描述当两个线程正在读写两个不同的 volatile 字段时弱内存模型体系结构允许的所有可能的内存访问重新排序,那么您对安全使用 volatile 的了解还不够,并且您会犯错误,因为您已经在这里完成,并编写一个非常脆弱的程序。

I was pretty sure a lock would be overkill in my situation

首先,最好的解决办法是干脆不去那里。如果您不编写尝试共享内存的多线程代码,那么您就不必担心锁定问题,这很难正确。

如果您必须编写共享内存的多线程代码,那么最好的做法是始终使用锁。锁几乎从不矫枉过正。无竞争锁的代价大约为十纳秒。您真的是在告诉我多十纳秒会对您的用户产生影响吗?如果是这样,那么您就有了一个非常非常快的程序和一个具有异常高标准的用户。

如果锁内的代码很昂贵,竞争锁的价格当然是任意高的。 不要在锁内做昂贵的工作,这样争用的概率很低。

只有当您遇到已证明的锁性能问题且无法通过消除争用来解决时,您才应该开始考虑低锁解决方案。

I added "volatile" to make sure that there is no misalignment occurring: reading only 32 bits of the variable and the other 32 bits on another fetch which can be broken in two by a write in the middle from another thread.

这句话告诉我,你现在需要停止编写多线程代码。多线程代码,尤其是低锁代码,仅供专家使用。在您再次开始编写多线程代码之前,您必须了解系统实际上是如何工作的。获得一本关于该主题的好书并努力学习。

你的句子是荒谬的,因为:

首先,整数已经只有 32 位了。

其次,规范保证 int 访问是原子的!如果你想要原子性,你已经做到了。

第三,是的,volatile 访问确实始终是原子的,但这并不是因为 C# 将所有 volatile 访问都变成了原子访问!相反,C# 规定将 volatile 放在字段上是非法的,除非该字段已经是原子的。

第四,volatile 的目的是防止 C# 编译器、抖动和 CPU 进行某些优化,这些优化会在弱内存模型中改变程序的含义。特别是 volatile 不会使++ 原子化。 (我在一家制造静态分析器的公司工作;我将使用您的代码作为我们的“可变字段上的非原子操作不正确”检查器的测试用例。这对我获得充满真实世界的代码非常有帮助现实的错误;我们想确保我们确实找到了人们写的错误,所以感谢您发布这个。)

查看您的实际代码:正如 Hans 指出的那样,volatile 完全不足以使您的代码正确。最好的做法是我之前说过的:不允许在主线程以外的任何线程上调用这些方法。反逻辑错误应该是您最不担心的事情。 如果另一个线程上的代码在序列化对象时修改对象的字段,那么序列化线程安全的原因是什么?这是您应该首先担心的问题。

关于c# - volatile 关键字用法与锁,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/19382705/

相关文章:

c# - 使用 Crystal Reports ReportDocument

c# - 将枚举值绑定(bind)到标签 XAML

performance - 是否可以估计 kmean 何时完成?

android - 如何检测 Android 设备是低端还是高端(如 moto e、moto g 或 Nexus 5x)

javascript - C# - 从代码隐藏中添加 javascript 函数

java - 阻止所有java线程消失在IO中的最简单方法?

java - 多线程中的 AES+HMAC 加密 - Java

c++ - 为什么 GetExitCodeThread() 在这里返回 FALSE?

performance - 在WebKit中有效循环大量固定位置图像的最佳方法是什么?

javascript - 将复杂对象从 View 发送到 MVC 操作作为非 ajax 调用