再次仔细检查锁定和C#

| 最近,我一直在重构一些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    
    }
}
我的观点是,没有其他线程应尝试再次初始化该项目,而一旦值初始化,许多线程应同时读取。如果获得了写锁,则可升级的读锁应阻止所有读取器,因此,在初始化对象时,其行为类似于在可升级的读锁开始处使用lock语句。初始化之后,可升级读锁将允许多个线程,因此不会出现等待每个线程的性能问题。 我在这里还读到一篇文章,说volatile导致在读取之前和写入之后自动插入内存屏障,因此我假设在读取和写入之间只有一个手动定义的屏障足以确保正确读取_valueReference对象。感谢您使用此方法的建议和批评。     
已邀请:
        为了强调@Mannimarco的要点:如果这是对Value的唯一访问点,并且看起来是这样,则整个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; 
    }
  }    
}
    
        警告:一次只有一个线程可以进入UpgradeableReadLock模式。查看ReaderWriterLockSlim。因此,如果在第一个线程进入写入模式并创建对象时线程堆积,那么您将遇到瓶颈,直到(希望)解决备份为止。我会严重建议使用静态初始化程序,它将使您的生活更轻松。 编辑:根据需要重新创建对象的频率,我实际上建议使用Monitor类及其Wait和Pulse方法。如果需要重新创建该值,则让线程在一个对象上等待并脉冲另一个对象,以使工作线程知道需要唤醒并创建新对象。创建对象后,PulseAll将允许所有读取器线程唤醒并获取新值。 (理论上)     

要回复问题请先登录注册