gpt4 book ai didi

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

转载 作者:行者123 更新时间:2023-11-30 23:23:33 25 4
gpt4 key购买 nike

假设你有一个属性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 并从中读取具有相同的效果),而在另一种情况下不使用?

edit TL;DR:如果一个线程通过 Interlocked.CompareExchange 更新字段的值,该更新的值是否会立即对其他执行非 volatile 操作的线程可见读取该字段?

最佳答案

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

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

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

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

虽然这可以通过两件事来缓解:

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

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

25 4 0
Copyright 2021 - 2024 cfsdn All Rights Reserved 蜀ICP备2022000587号
广告合作:1813099741@qq.com 6ren.com