再次双重检查锁定和C#

Iva*_*vov 5 c# locking readerwriterlockslim double-checked-locking

最近我一直在重构我的一些C#代码,我发现了一些双重检查锁定实践.那时我不知道这是一个不好的做法,我真的想摆脱它.

问题是我有一个应该被懒惰地初始化并且经常被许多线程访问的类.我也不想将初始化移动到静态初始化器,因为我计划使用弱引用来保持初始化对象在内存中保持太长时间.但是,如果需要,我想"恢复"对象,确保以线程安全的方式发生这种情况.

我想知道是否在C#中使用ReaderWriterLockSlim并在第一次检查之前输入UpgradeableReadLock,然后如果需要,为初始化输入写锁定将是可接受的解决方案.以下是我的想法:

public class LazyInitialized
{
    private readonly ReaderWriterLockSlim _lock = new ReaderWriterLockSlim();

    private volatile WeakReference _valueReference = new WeakReference(null);
    public MyType Value
    {
        get
        {
            MyType value = _valueReference.Target as MyType;
            _lock.EnterUpgradeableReadLock();
            try
            {
                if (!_valueReference.IsAlive) // needs initializing
                {
                    _lock.EnterWriteLock();
                    try
                    {
                        if (!_valueReference.IsAlive) // check again
                        {
                            // prevent reading the old weak reference
                            Thread.MemoryBarrier(); 
                            _valueReference = new WeakReference(value = InitializeMyType());
                        }
                    }
                    finally
                    {
                        _lock.ExitWriteLock();
                    }
                }
            }
            finally
            {
                _lock.ExitUpgradeableReadLock();
            }
            return value;
        }       
    }

    private MyType InitializeMyType()
    {
        // code not shown    
    }
}
Run Code Online (Sandbox Code Playgroud)

我的观点是,没有其他线程应该尝试再次初始化项目,而一旦初始化值,许多线程应该同时读取.如果获取了写锁,则可升级读锁应阻止所有读取器,因此在初始化对象时,行为类似于具有可升级读锁开始的锁语句.初始化之后,可升级读锁将允许多个线程,因此不会出现等待每个线程的性能损失.

我还在这里读了一篇文章,说volatile会在读取之前和写入之后自动插入内存屏障,所以我假设读取和写入之间只有一个手动定义的屏障就足以确保正确读取_valueReference对象.我很乐意感谢您使用这种方法的建议和批评.

Hen*_*man 2

为了强调 @Mannimarco 提出的观点:如果这是该值的唯一访问点,并且看起来是这样,那么您的整个 ReaderWriterLockSlim 设置并不比简单的 Monitor.Enter / Monitor.Leave 方法更好。但情况要复杂得多。

所以我相信下面的代码在功能和效率上是等效的:

private WeakReference _valueReference = new WeakReference(null);
private object _locker = new object();

public MyType Value
{    
  get
  {    
    lock(_locker)  // also provides the barriers
    {
        value = _valueReference.Target;

        if (!_valueReference.IsAlive)
        {
            _valueReference = new WeakReference(value = InitializeMyType());
        }
        return value; 
    }
  }    
}
Run Code Online (Sandbox Code Playgroud)