UpgradeableReadLock的合法用例

时间:2014-07-17 12:33:05

标签: c# .net concurrency thread-safety locking

我最近想起了C#提供的UpgradeableReadLock构造,并且我试图辨别何时使用它真的很有意义。

比如说,我有很多类很多读取的设置缓存,但是需要根据一组不一定确定性的条件定期更新频率......

简单地锁定就更有意义了:

List<Setting> cachedSettings = this.GetCachedSettings( sessionId );

lock(cachedSettings)
{
    bool requiresRefresh = cachedSettings.RequiresUpdate();
    if(requiresRefresh)
    {
        // a potentially long operation
        UpdateSettings( cachedSettings, sessionId );
    }

    return cachedSettings;
}

或使用UpgradeableReadLock:

public class SomeRepitory {

private ReaderWriterLockSlim _rw = new ReaderWriterLockSlim();

public List<Setting> GetCachedSettings( string sessionId )
{
    _rw.EnterUpgradeableReadLock();

    List<Setting> cachedSettings = this.GetCachedSettings( sessionId );

    bool requiresRefresh = cachedSettings.RequiresUpdate();
    if(requiresRefresh)
    {
        _rw.EnterWriteLock();

        UpdateSettings( cachedSettings, sessionId );

        _rw.ExitWriteLock();
    }

    _rw.ExitUpgradeableReadLock();

    return cachedSettings;
}

也许让我感到困惑的是我们如何能够在写入块之外检查是否需要更新。在我上面的示例中,我指的是当我检查需要刷新的位置时,但为了简化我将使用&#34; C#5.0在一个坚果壳中的示例&#34;:

while (true)
{
    int newNumber = GetRandNum (100); 
    _rw.EnterUpgradeableReadLock(); 
    if (!_items.Contains (newNumber)) 
    {
        _rw.EnterWriteLock();
        _items.Add (newNumber);
        _rw.ExitWriteLock();
        Console.WriteLine ("Thread " + threadID + " added " + newNumber);
    }
    _rw.ExitUpgradeableReadLock();
    Thread.Sleep (100);
}

我的理解是,这允许并发读取,除非线程需要写入,但如果两个或多个线程以相同的随机数结束并确定!_items.Contains(newNumber)怎么办?鉴于我的理解,这应该允许并发读取(当然,如果我误解了,请纠正我)..似乎,只要获得写锁定,任何同时读取的线程都需要暂停并强制返回到_rw.EnterUpgradeableReadLock();的开头?

1 个答案:

答案 0 :(得分:2)

当然,如果有许多同时读者和相对罕见的写操作,你的第二种方法会更好。当线程获取读锁定(使用_rw.EnterUpgradeableReadLock())时 - 其他线程也可以获取它并同时读取值。当某个线程进入写锁定时,它等待所有读取完成,然后获取对锁定对象的独占访问权限(所有其他线程试图执行EnterXXX()操作等待)来更新该值。当它释放锁时,其他线程可以完成它们的工作。

第一个示例lock(cachedSettings)阻止所有其他线程,以便一次只能读取一个线程。

我建议另外使用以下模式:

_rw.EnterUpgradeableReadLock();
try
{
    //Do your job
}
finally
{
    _rw.ExitUpgradeableReadLock();
}

用于所有进入/退出锁定操作。它确保(with high probability)如果在同步代码中发生异常,则锁定将不会永久锁定。

修改 回答马丁的评论。如果您不希望多个线程同时更新该值,则需要更改逻辑以实现该目标。例如,使用double-checked lock构造:

if(cachedSettings.RequiresUpdate())
{
    _rw.EnterWriteLock();
    try
    {
        if(cachedSettings.RequiresUpdate())
        {
            UpdateSettings( cachedSettings, sessionId );
        }
    }
    finally
    {
        _rw.ExitWriteLock();
    }
}

这将检查我们是否在等待写锁定时其他线程还没有刷新该值。如果值不再需要刷新 - 只需释放锁定。

重要提示:长时间使用独占锁是非常糟糕的。所以UpdateSettings函数是长时间运行的,你最好在锁之外执行它,并实现一些额外的逻辑,以允许读者读取过期值,而某些线程正在刷新它。我过去常常实现一次缓存,而且使它快速且线程安全非常复杂。您最好使用其中一个现有实现(例如System.Runtime.MemoryCache)。