c# - 对 Interlocked.CompareExchange 延迟初始化的字段执行定期读取是否正确?

标签 c# .net multithreading thread-safety volatile

假设您有特性public Foo Bar { get; }您想要延迟初始化。一种这样的方法可能是使用 Interlocked类,它保证某些操作序列的原子性(例如递增、添加、比较交换)。你可以这样做:

private Foo _bar;

public Foo Bar
{
    get
    {
        // Initial check, necessary so we don't call new Foo() every time when invoking the method
        if (_bar == null)
        {
            // In the unlikely case 2 threads come in here
            // at the same time, new Foo() will simply be called
            // twice, but only one of them will be set to _bar
            Interlocked.CompareExchange(ref _bar, new Foo(), null);
        }
        return _bar;
    }
}

有很多地方演示了这种延迟初始化的方法,例如this blogplaces in the .NET Framework itself .

我的问题是,不应该读取 _bar不稳定?例如,线程 1 可能调用 CompareExchange ,设置_bar的值,但该更改对线程 2 不可见,因为(如果我正确理解 this question )它可能已缓存 _bar 的值为 null,它最终可能会调用 Interlocked.CompareExchange再次,尽管线程 1 已经设置了 _bar 。所以不应该_bar被标记为 volatile ,以防止这种情况发生?

简而言之,就是this approachthis approach延迟初始化正确吗?为什么是Volatile.Read (与将变量标记为 volatile 并从中读取具有相同的效果)在一种情况下使用,但在另一种情况下不使用?

编辑TL;DR:如果一个线程通过 Interlocked.CompareExchange 更新字段的值,执行该字段的非 volatile 读取的其他线程会立即看到更新后的值吗?

最佳答案

我的第一个想法是“谁在乎呢?” :)

我的意思是:双重检查初始化模式几乎总是矫枉过正,而且很容易出错。大多数时候,一个简单的lock最好:易于编写,性能足够,并且清楚地表达了代码的意图。此外,我们现在有Lazy<T>类来抽象延迟初始化,进一步消除了我们手动实现代码来执行此操作的任何需要。

因此,双重检查模式的细节并不是那么重要,因为我们无论如何都不应该使用它。

也就是说,我同意您的观点,即读取应该是 volatile 读取。如果没有它, Interlocked.CompareExchange() 提供的内存屏障不一定有帮助。

不过有两件事可以缓解这个问题:

  1. 无论如何都不能保证双重检查模式。即使您有 volatile 读取,也存在竞争条件,因此必须安全地初始化两次。因此,即使内存位置已过时,也不会发生真正糟糕的情况。您将调用Foo构造函数两次,这不太好,但它不会是一个致命的问题,因为无论如何都有可能发生。
  2. 在 x86 上,默认情况下内存访问是 volatile 的。因此,实际上只有在其他平台上这才会成为问题。

关于c# - 对 Interlocked.CompareExchange 延迟初始化的字段执行定期读取是否正确?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/38298212/

相关文章:

c# - 复杂 Xamarin Forms 自定义控件中的内容

.NET:是否有 CSS 媒体类型的系统枚举?

javascript - 下载 asp.net 后触发 javascript 函数

multithreading - pthread mutex (un)locking over different threads

c# - .NET Core 上的 OpenFileDialog

c# - 删除字符串末尾的系列的所有实例

c# - 从字符串中检索货币符号

c# - 在不使用互操作/非托管代码的情况下监视打印假脱机

java - 为什么 CopyOnWriteArrayList 是安全的?

.net - TcpListener 超时/关于/某事?没有异步?