[英]Memory read visibility ordering vs write order using double-checked locking
我有以下函数,旨在“memoize”无参数函数。 意思是只调用一次函数,然后在其他时间返回相同的结果。
private static Func<T> Memoize<T>(Func<T> func)
{
var lockObject = new object();
var value = default(T);
var inited = false;
return () => {
if (inited)
return value;
lock (lockObject) {
if (!inited) {
value = func();
inited = true;
}
}
return value;
};
}
我可以确定如果一个线程在锁外读取“inited == true”,那么它将读取在“inited”设置为true之前写入的“值”吗?
注意: .NET中的双重检查锁定涵盖了它应该工作的事实,这个问题主要是检查我的实现是否正确并且可能获得更好的替代方案。
不,因为inited
volatile
。 volatile
为您提供内存释放并获取您需要的围栏,以便建立正确的先发生关系。
如果在inited
之前没有释放围栏设置为true,那么在另一个线程读inited
并且将其视为真时,该value
可能不会完全写入,这可能导致返回半构造的对象。 同样,如果有一个释放的围栏,但读数之前没有相应的获取栅栏inited
在第一次检查,有可能是该对象被完全构造,但是,看到CPU核心inited
为真还没有见过的记忆效应value
感写入(缓存一致性并不一定要求在其他核心上按顺序看到连续写入的影响)。 这将再次潜在地导致返回半构造的对象。
顺便说一下,这是已经非常详细记录的双重检查锁定模式的实例。
我建议不要使用捕获局部变量的lambda(这会使编译器生成一个隐式类来保存非易失性字段中的闭合变量),而是建议使用volatile
属性value
明确创建自己的类。
private class Memoized<T>
{
public T value;
public volatile bool inited;
}
private static Func<T> Memoize<T>(Func<T> func)
{
var memoized = new Memoized<T>();
return () => {
if (memoized.inited)
return memoized.value;
lock (memoized) {
if (!memoized.inited) {
memoized.value = func();
memoized.inited = true;
}
}
return memoized.value;
};
}
当然,正如其他人所说, Lazy<T>
为了这个目的而存在的。 使用它而不是自己动手,但了解事物如何运作的理论总是一个好主意。
我认为你最好使用标准的Lazy<T>
类来实现你需要的功能,如:
private static Func<T> Memoize<T>(Func<T> func)
{
var lazyValue = new Lazy<T>(func, isThreadSafe: true);
return () => lazyValue.Value;
}
不,那段代码不安全。 编译器可以自由地inited
对value
和inited
的写入; 内存系统也是如此。 这意味着另一个线程可能会将inited
设置为true
而value
仍然是默认值。
这种模式称为双重检查锁定,Albahari在Lazy Initialization下讨论。 建议的解决方案是使用内置的Lazy<T>
类。 等效实现如下:
private static Func<T> Memoize<T>(Func<T> func)
{
var lazy = new Lazy<T>(func);
return () => lazy.Value;
}
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.