使用ReaderWriterLockSlim
?
考虑这种情况:我有dictionary
。事情可以添加到它。但事情无法从中消除。当添加东西时,它在时间方面可能是一个非常昂贵的操作(只有几百毫秒,但相对于应用程序的其余部分仍然是昂贵的)如果我想添加一些东西但它还没有,那么会不会任何可以获得的东西:
如下所示:
void populateIfNotPresent( object thing )
{
_lock.EnterReadLock( ) ;
bool there = _dictionary.ContainsKey(thing);
_lock.ExitReadLock( ) ;
// Remember, the specs say nothing can be removed from this dictionary.
if (!there)
{
_lock.EnterUpgradeableReadLock( ) ;
try
{
if( !_dictionary.ContainsKey( thing ) )
{
_lock.EnterWriteLock( ) ;
try
{
populate( thing ) ;
}
finally
{
_lock.ExitWriteLock( ) ;
}
}
}
finally
{
_lock.ExitUpgradeableReadLock( ) ;
}
}
}
文档说一次只有一个线程可以进入可升级的读锁定,但不会阻止任何其他线程进入读锁定,因此似乎
答案 0 :(得分:4)
ReaderWriterLockSlim
class(与任何其他读写器锁一样)用于与写入次数相比的大量读取。
你在做什么实际上是三重检查,这是多余的;你也可以输入一个不可擦除的写锁。如果该项存在,则退出锁,否则,升级到写锁。
您的方法表明读取没有提供任何值,因为您很有可能执行写入。由于可升级写入不会阻止任何其他读取,因此不应该在此处杀死您。
但是,如果这是你正在进行读/写的唯一的地方(或者大多数发生在这里)那么就会出现问题,你的读/写比率不够高保证读/写锁定,你应该寻找其他同步方法。
那就是说,最后,一切都是为了测试性能;如果你要优化一个实现,你需要测量它的当前性能,以便进行比较,否则,它只是premature optimization。
答案 1 :(得分:1)
所以看起来双重检查锁中有值。
价值多少?好吧,如果您希望看到大量缓存未命中,那么执行可升级锁可能是有意义的;但是,如果你不期望看到很多缓存未命中,那么你就是在进行不必要的锁定。总的来说,我会选择最简单的解决方案来完成工作。优化锁定通常不会让你获得最大的收益,寻找更大的东西来优化。
<强>建议:强>
可能会给你带来更多好处的东西是条纹词典(Java's StripedMap是一个非常好的起点,它应该不是很难理解)。
StripedMap
/ StripedDictionary
背后的基本思想是你有一系列锁:
object[] syncs = new object[n]();
// also create n new objects
您应该使用足够多的条带对地图进行条带化,以便允许您必须在没有冲突的情况下输入方法的线程数。我没有任何数据支持这一点,但假设您最多需要8个线程才能进入地图,那么您可能会使用8个或更多个锁(条带)以确保所有8个线程都可以进入同时映射。如果你想要更好的“保险”来对抗“碰撞”,那就创造更多的条纹,比如32或64。
当您输入populateIfNotPresent
方法时,根据哈希码锁定其中一个锁:
void populateIfNotPresent( object thing )
{
lock(syncs[thing.GetHashCode()%syncs.Length])
{
if(!dictionary.ContainsKey(thing))
{
populate(thing);
}
}
}
假设您有8个条带,现在您允许最多8个线程安全地进入并执行昂贵的操作,否则会阻塞其他7个线程。当然,假设散列函数足够强大,可以提供重复概率较低的散列。
你已经期望populateIfNotPresent
价格昂贵如果该项目不存在,但是如果你有一个条带字典,那么你可以让多个线程在字典的不同扇区上工作而不用碰到对方。这将为您提供更大的好处,然后从检查对象是否存在中削减几个CPU周期,因为昂贵的操作是在 对象存在时。