Double Check Lock 模式是否应该与 ReaderWriteLockSlim 一起使用?
使用ReaderWriterLockSlim
时是否需要双重检查锁定模式?
考虑这种情况:我有一个字典
。东西可以添加到其中。但东西无法从中删除。当添加东西时,就时间而言,它可能是一个非常昂贵的操作(只有数百毫秒,但相对于应用程序的其余部分来说仍然昂贵)如果我想添加一些东西,但它还不存在,是否会有通过以下方式可以获得什么:
- 首先获取读锁,然后检查是否存在,
- 然后输入可升级的读锁,然后再次检查,
- 然后如果字典中仍然不存在该项目,则输入写锁?
类似于以下内容:
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( ) ;
}
}
}
文档说一次只能有一个线程可以进入可升级的读锁,但不会阻止任何其他线程进入读锁,因此看起来有价值双重检查锁。
你怎么认为?这是否太过分了?
Is the double check lock pattern necessary when using ReaderWriterLockSlim
?
Consider this scenario: I have a dictionary
. Things can be added to it. But things can't be removed from it. When things are added, it can be a very expensive operation in terms of time (only hundreds of milliseconds, but still costly relative to the rest of the app) If I wanted to add something and it wasn't already there, would there be anything to gain by:
- first acquiring a read lock, then checking for existence,
- then entering an upgradable read lock, and checking again,
- then entering a write lock if the item is still not present in the dictionary?
Something like the following:
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( ) ;
}
}
}
The docs say only one thread at a time can enter an upgradable read lock, but doesn't stop any other threads from entering a read lock, so it appears that there is value in the double check lock.
What do you think? Is this overkill?
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论
评论(2)
ReaderWriterLockSlim
类 (与任何其他读写锁一样)意味着与写入次数相比,读取次数较多。你所做的实际上是三重检查,这是多余的;你不妨输入一个不可分级的写锁。如果该项存在,则退出锁,否则,升级为写锁。
您的方法表明读取在这里没有提供任何价值,因为您很有可能执行写入。由于可升级写入不会阻止任何其他读取,因此它不会在这里杀死您。
但是,如果这是您进行读/写的唯一位置(或者大部分发生在这里),那么就会出现问题,您的读/写比率不够高,不足以保证读取/写锁定,您应该寻找其他同步方法。
也就是说,最终,一切都是为了测试性能;如果你要优化一个实现,你需要测量它的当前性能以便进行比较,否则,它只是过早的优化。
The
ReaderWriterLockSlim
class (like any other reader-writer lock) is meant for a high number of reads compared to the number of writes.What you are doing is actually triple-checking, and it's superfluous; you might as well enter an ungradable write lock. If the item exists, then exit the lock, otherwise, upgrade to a write lock.
Your method indicates that the read is not providing any value here, since you have a good possibility of performing the write. Since the upgradable write won't block any other reads, it shouldn't kill you here.
However, if this is the only place that you are doing reads/writes (or the majority take place here) then there's a problem, your ratio of reads/writes isn't high enough to warrant a read/write lock and you should look to some other synchronization method.
That said, in the end, it's all about testing the performance; if you're going to optimize an implementation, you need to measure it's current performance in order to make a comparison against, otherwise, it's just premature optimization.
价值多少?好吧,如果您期望看到大量缓存未命中,那么执行可升级锁可能是有意义的;但是,如果您不期望看到大量缓存未命中,那么您就进行了不必要的锁定。一般来说,我会采用尽可能简单的解决方案来完成工作。优化锁通常不会让您获得最大的收益,首先要寻找更大的东西来优化。
建议:
可能会给你带来更多好处的是 Striped Dictionary (Java 的 StripedMap 是一个非常好的起点,并且应该不难理解)。
StripedMap
/StripedDictionary
背后的基本思想是您有一个锁数组:您应该使用足够多的条带对地图进行条带化,以便允许线程数量你必须在没有碰撞的情况下进入该方法。我没有任何数据来支持这一点,但假设您预计最多有 8 个线程进入映射,那么您可能可以使用 8 个或更多锁(条带)以确保所有 8 个线程都可以进入映射同时映射。如果您想要更好地“确保”防止“冲突”,请创建更多条带,例如 32 或 64。
当您输入
populateIfNotPresent
方法时,您将根据哈希码锁定其中一个锁:< strong>假设您有 8 个条带,现在您允许最多 8 个线程安全地进入并执行昂贵的操作,否则这会阻塞其他 7 个线程。当然,假设是散列函数足够强大,可以提供低重复概率的散列。
您已经预计
populateIfNotPresent
会很昂贵 如果该项目不存在,但如果您有一个条带字典,那么您可以让多个线程在字典的不同扇区上工作,而无需互相碰撞。与检查对象是否存在相比,这会给您带来更大的好处,因为昂贵的操作发生在对象确实存在时。How much value? Well, if you're expecting to see a lot of cache misses, then it might make sense to perform the upgradable lock; however, if you're not expecting to see a lot of cache misses, then you're doing needless locking. In general, I would go with the simplest solution possible that gets the job done. Optimizing the locks is usually not where you will get the biggest bang for your buck, look for bigger things to optimize first.
Suggestion:
Something that might give you a lot more bang for your buck is a Striped Dictionary (Java's StripedMap is a pretty good place to start and it shouldn't be very hard to understand).
The basic idea behind the
StripedMap
/StripedDictionary
is that you have an array of locks:You should stripe your map with sufficiently large number of stripes in order to allow the number of threads you have to enter the method without collision. I don't have any data to back this up, but suppose you're expecting up to 8 threads to enter the map, then you could probably use 8 or more locks (stripes) in order to ensure that all 8 threads can enter the map simultaneously. If you want better "insurance" against "collisions", then create more stripes, say 32 or 64.
When you enter the
populateIfNotPresent
method, you lock on one of those locks depending on the hash code:Suppose you have 8 stripes, now you're allowing up to 8 threads to safely enter and do an expensive operation, which would have otherwise blocked the other 7 threads. The assumption, of course, is that the hashing function is robust enough to provide hashes with low probability of duplication.
You already expect
populateIfNotPresent
to be expensive IF the item is not present, but if you have a striped dictionary, then you can have multiple threads work on different sectors of the dictionary without bumping into each other. This will give you a much greater benefit then shaving off a couple of CPU cycles from checking if the object exists, because the expensive operation is when the object does exist.