我具有以下旨在“记住”无参数功能的功能。意思是只调用一次函数,然后其他所有时间都返回相同的结果。

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”,那么它将读取在“initial”设置为true之前写入的“value”?

注意:Double-checked locking in .NET涵盖了它应该可以工作的事实,这个问题主要是为了检查我的实现是否正确并可能获得更好的替代方法。

最佳答案

否,因为inited不是volatilevolatile为您提供内存释放并获取您需要的围墙,以便建立正确的事前-事前关系。

如果在将inited设置为true之前没有释放屏障,则在另一个线程读取value并将其视为true时,可能无法完全写入inited,这可能会导致返回半构建的对象。同样,如果在第一次检查中读取inited之前有释放隔离区但没有相应的获取隔离区,则可能对象已完全构建,但是将inited视为true的CPU内核尚未看到value的内存影响。写入(缓存一致性并不一定要求在其他内核上按顺序看到连续写入的效果)。这将再次有可能导致返回一半构造的对象。

顺便说一句,这是一个已经很好地记录在案的双重检查锁定模式的实例。

我建议不要使用捕获局部变量的lambda(这将使编译器生成一个隐式类以将封闭变量保存在非 volatile 字段中),而是建议使用为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>正是为此目的而存在的。使用它而不是自己动手做,但是了解某些事物的工作原理始终是一个好主意。

关于c# - 使用双重检查锁定的内存读取可见性顺序与写入顺序,我们在Stack Overflow上找到一个类似的问题:https://stackoverflow.com/questions/29901967/

10-10 13:47