Double Check Lock模式是否应与ReaderWriterLockSlim一起使用?

时间:2011-08-04 19:33:23

标签: .net multithreading thread-safety

使用ReaderWriterLockSlim

时,是否需要双重检查锁定模式

考虑这种情况:我有dictionary。事情可以添加到它。但事情无法从中消除。当添加东西时,它在时间方面可能是一个非常昂贵的操作(只有几百毫秒,但相对于应用程序的其余部分仍然是昂贵的)如果我想添加一些东西但它还没有,那么会不会任何可以获得的东西:

  1. 首先获取读锁定,然后检查是否存在,
  2. 然后输入可升级的读锁定,再次检查
  3. 然后输入写锁定,如果该项目仍未出现在字典中?
  4. 如下所示:

    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( ) ;
                }
            }
    }
    

    文档说一次只有一个线程可以进入可升级的读锁定,但不会阻止任何其他线程进入读锁定,因此似乎中的值检查锁。

    你怎么看?这有点过头了吗?

2 个答案:

答案 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周期,因为昂贵的操作是在 对象存在时。