最近,我一直在重构一些 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
}
}
我的观点是,任何其他线程都不应该尝试再次初始化该项目,而一旦初始化该值,许多线程应该同时读取。如果获取了写锁,可升级读锁应该阻塞所有读取器,因此在初始化对象时,行为将类似于在可升级读锁开始处使用一个锁定语句。初始化后,可升级读锁将允许多个线程,因此不会出现等待每个线程的性能影响。
我还看过一篇文章here http://www.albahari.com/threading/part4.aspx说 volatile 会导致在读取之前和写入之后自动插入内存屏障,因此我假设读取和写入之间只有一个手动定义的屏障就足以确保正确读取 _valueReference 对象。我很乐意感谢您对使用这种方法的建议和批评。
为了强调@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;
}
}
}
本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)