并发数据结构 : .NET Framework 中提供的读写锁 ReaderWriterLockSlim 类

转自;http://www.cnblogs.com/lucifer1982/archive/2008/12/07/1349437.html

ReaderWriterLockSlim 类

新的 ReaderWriterLockSlim 类支持三种锁定模式:Read,Write,UpgradeableRead。这三种模式对应的方法分别是 EnterReadLock,EnterWriteLock,EnterUpgradeableReadLock 。再就是与此对应的 TryEnterReadLock,TryEnterWriteLock,TryEnterUpgradeableReadLock,ExitReadLock,ExitWriteLock,ExitUpgradeableReadLock。Read 和 Writer 锁定模式比较简单易懂:Read 模式是典型的共享锁定模式,任意数量的线程都可以在该模式下同时获得锁;Writer 模式则是互斥模式,在该模式下只允许一个线程进入该锁。UpgradeableRead 锁定模式可能对于大多数人来说比较新鲜,但是在数据库领域却众所周知。

这个新的读写锁类性能跟 Monitor 类大致相当,大概在 Monitor 类的 2 倍之内。而且新锁优先让写线程获得锁,因为写操作的频率远小于读操作。通常这会导致更好的可伸缩性。起初,ReaderWriterLockSlim 类在设计时考虑到相当多的情况。比如在早期 CTP 的代码还提供了PrefersReaders, PrefersWritersAndUpgrades 和 Fifo 等竞争策略。但是这些策略虽然添加起来非常简单,但是会导致情况非常的复杂。所以 Microsoft 最后决定提供一个能够在大多数情况下良好工作的简单模型。

ReaderWriterLockSlim 的更新锁

现在让我们更加深入的讨论一下更新模型。UpgradeableRead 锁定模式允许安全的从 Read 或 Write 模式下更新。还记得先前 ReaderWriterLock 的更新是非原子性,危险的操作吗(尤其是大多数人根本没有意识到这点)?现在提供的新读写锁既不会破坏原子性,也不会导致死锁。新锁一次只允许一个线程处在 UpgradeableRead 模式下。

一旦该读写锁处在 UpgradeableRead 模式下,线程就能读取某些状态值来决定是否降级到 Read 模式或升级到 Write 模式。注意应当尽可能快的作出这个决定:持有 UpgradeableRead 锁会强制任何新的读请求等待,尽管已存在的读取操作仍然活跃。遗憾的是,CLR 团队移除了 DowngradeToRead 和 UpgradeToWrite 两个方法。如果要降级到读锁,只要简单的在 ExitUpgradeableReadLock 方法后紧跟着调用 EnterReadLock 方法即可:这可以让其他的 Read 和 UpgradeableRead 获得完成先前应当持有却被 UpgradeableRead 锁持有的操作。如果要升级到写锁,只要简单调用 EnterWriteLock 方法即可:这可能要等待,直到不再有任何线程在 Read 模式下持有锁。不像降级到读锁,必须调用 ExitUpgradeableReadLock。在 Write 模式下不必非得调用 ExitUpgradeableReadLock。但是为了形式统一,最好还是调用它。比如下面的代码:

using System;
using System.Linq;
using System.Threading;

namespace Lucifer.CSharp.Sample
{
    class Program
    {
        private ReaderWriterLockSlim rwLock = new ReaderWriterLockSlim();

        void Sample()
        {
            bool isUpdated = true;
            rwLock.EnterUpgradeableReadLock();
            try 
            {
                if (/* … 读取状态值来决定是否更新 … */) 
                {
                    rwLock.EnterWriteLock();
                    try 
                    {
                        //… 写入状态值 …
                    } 
                    finally 
                    {
                        rwLock.ExitWriteLock();
                    }
                } 
                else 
                {
                    rwLock.EnterReadLock();
                    rwLock.ExitUpgradeableReadLock();
                    isUpdated = false;
                    try 
                    {
                        //… 读取状态值 …
                    } 
                    finally 
                    {
                        rwLock.ExitReadLock();
                    }
                }
            } 
            finally 
            {
                if (isUpdated)
                    rwLock.ExitUpgradeableReadLock();
            }
        }
    }
}

ReaderWriterLockSlim 的递归策略

新的读写锁还有一个有意思的特性就是它的递归策略。默认情况下,除已提及的降级到读锁和升级到写锁之外,所有的递归请求都不允许。这意味着你不能连续两次调用 EnterReadLock,其他模式下也类似。如果你这么做,CLR 将会抛出 LockRecursionException 异常。当然,你可以使用 LockRecursionPolicy.SupportsRecursion 的构造函数参数让该读写锁支持递归锁定。但不建议对新的开发使用递归,因为递归会带来不必要的复杂情况,从而使你的代码更容易出现死锁现象。

有一种特殊的情况永远也不被允许,无论你采取什么样的递归策略。这就是当线程持有读锁时请求写锁。Microsoft 曾经考虑提供这样的支持,但是这种情况太容易导致死锁。所以 Microsoft 最终放弃了这个方案。

此外,这个新的读写锁还提供了很多对应的属性来确定线程是否在指定模型下持有该锁。比如 IsReadLockHeld, IsWriteLockHeld 和 IsUpgradeableReadLockHeld 。你也可以通过 WaitingReadCount,WaitingWriteCount 和 WaitingUpgradeCount 等属性来查看有多少线程正在等待持有特定模式下的锁。CurrentReadCount 属性则告知目前有多少并发读线程。RecursiveReadCount, RecursiveWriteCount 和 RecursiveUpgradeCount 则告知目前线程进入特定模式锁定状态下的次数。

小结

这篇文章分析了 .NET 中提供的两个读写锁类。然而 .NET 3.5 提供的新读写锁 ReaderWriterLockSlim 类消除了 ReaderWriterLock 类存在的主要问题。与 ReaderWriterLock 相比,性能有了极大提高。更新具有原子性,也可以极大避免死锁。更有清晰的递归策略。在任何情况下,我们都应该使用 ReaderWriterLockSlim 来代替 ReaderWriterLock 类。

郑重声明:本站内容如果来自互联网及其他传播媒体,其版权均属原媒体及文章作者所有。转载目的在于传递更多信息及用于网络分享,并不代表本站赞同其观点和对其真实性负责,也不构成任何其他建议。