C# ReaderWriterLockSlim 实现

其实ReaderWriterLockSlim的实现前段时间看了,当时不打算记录下来的,因为它的实现实在System.Core项目里面,而不是mscorlib项目。按照惯例我们还是先看看网上的一些说法吧。

读写锁 ReaderWriterLock 、就是支持单个写线程和多个读线程的锁。自.NET 3.5 开始 ReaderWriterLockSlim登上舞台,ReaderWriterLockSlim 可以看做是 ReaderWriterLock 的升级版。 由于 ReaderWriterLockSlim 默认不支持递归调用、所以在某种意义上来说更不容易造成死锁。
ReaderWriterLockSlim 类支持三种锁定模式:Read,Write,UpgradeableRead。这三种模式对应的方法分别是 EnterReadLock,EnterWriteLock,EnterUpgradeableReadLock 。再就是与此对应的 TryEnterReadLock,TryEnterWriteLock,TryEnterUpgradeableReadLock,ExitReadLock,ExitWriteLock,ExitUpgradeableReadLock。Read 和 Writer 锁定模式比较简单易懂:Read 模式是典型的共享锁定模式,任意数量的线程都可以在该模式下同时获得锁;Writer 模式则是互斥模式,在该模式下只允许一个线程进入该锁。UpgradeableRead 锁定模式可能对于大多数人来说比较新鲜,但是在数据库领域却众所周知。
备注及注意事项
1、对于同一把锁、多个线程可同时进入读模式。
2、对于同一把锁、同时只允许一个线程进入写模式。
3、对于同一把锁、同时只允许一个线程进入可升级的读模式。
4、通过默认构造函数创建的读写锁是不支持递归的,若想支持递归 可通过构造 ReaderWriterLockSlim(LockRecursionPolicy) 创建实例。
5、对于同一把锁、同一线程不可两次进入同一锁状态(开启递归后可以)
6、对于同一把锁、即便开启了递归、也不可以在进入读模式后再次进入写模式或者可升级的读模式(在这之前必须退出读模式)。
7、再次强调、不建议启用递归。
8、读写锁具有线程关联性,即两个线程间拥有的锁的状态相互独立不受影响、并且不能相互修改其锁的状态。
9、升级状态:在进入可升级的读模式 EnterUpgradeableReadLock后,可在恰当时间点通过EnterWriteLock进入写模式。
10、降级状态:可升级的读模式可以降级为读模式:即在进入可升级的读模式EnterUpgradeableReadLock后, 通过首先调用读取模式EnterReadLock方法,然后再调用 ExitUpgradeableReadLock 方法

实现code如下:

public enum LockRecursionPolicy
{
    NoRecursion = 0,
    SupportsRecursion = 1,
}
internal class ReaderWriterCount
{    
    public long lockID;
    public int readercount;
    public int writercount;
    public int upgradecount;
    public ReaderWriterCount next;
}

public class ReaderWriterLockSlim : IDisposable
{
    //Specifying if the lock can be reacquired recursively.
    bool fIsReentrant;
    int myLock;

    //The variables controlling spinning behavior of Mylock(which is a spin-lock)
    const int LockSpinCycles = 20;
    const int LockSpinCount = 10;
    const int LockSleep0Count = 5;

    // These variables allow use to avoid Setting events (which is expensive) if we don't have to. 
    uint numWriteWaiters;        // maximum number of threads that can be doing a WaitOne on the writeEvent 
    uint numReadWaiters;         // maximum number of threads that can be doing a WaitOne on the readEvent
    uint numWriteUpgradeWaiters;      // maximum number of threads that can be doing a WaitOne on the upgradeEvent (at most 1). 
    uint numUpgradeWaiters;

    //Variable used for quick check when there are no waiters.
    bool fNoWaiters;

    int upgradeLockOwnerId;
    int writeLockOwnerId;

    // conditions we wait on. 
    EventWaitHandle writeEvent;    // threads waiting to acquire a write lock go here.
    EventWaitHandle readEvent;     // threads waiting to acquire a read lock go here (will be released in bulk)
    EventWaitHandle upgradeEvent;  // thread waiting to acquire the upgrade lock
    EventWaitHandle waitUpgradeEvent;  // thread waiting to upgrade from the upgrade lock to a write lock go here (at most one)

    // Every lock instance has a unique ID, which is used by ReaderWriterCount to associate itself with the lock
    // without holding a reference to it.
    static long s_nextLockID;
    long lockID;

    // See comments on ReaderWriterCount.
    [ThreadStatic]
    static ReaderWriterCount t_rwc;

    bool fUpgradeThreadHoldingRead;

    private const int MaxSpinCount = 20;

    //The uint, that contains info like if the writer lock is held, num of readers etc.
    uint owners;

    private const uint WRITER_HELD = 0x80000000;
    private const uint WAITING_WRITERS = 0x40000000;
    private const uint WAITING_UPGRADER = 0x20000000;

    private const uint MAX_READER = 0x10000000 - 2;
    private const uint READER_MASK = 0x10000000 - 1;
    private bool fDisposed;
    
    public ReaderWriterLockSlim() : this(LockRecursionPolicy.NoRecursion){}

    public ReaderWriterLockSlim(LockRecursionPolicy recursionPolicy)            
    {
        if (recursionPolicy == LockRecursionPolicy.SupportsRecursion)
        {
            fIsReentrant = true;
        }            
        InitializeThreadCounts();
        fNoWaiters = true;
        lockID = Interlocked.Increment(ref s_nextLockID);
    }
    
    private void InitializeThreadCounts()
    {
        upgradeLockOwnerId = -1;
        writeLockOwnerId = -1;
    }
    
    public void EnterReadLock()
    {
        TryEnterReadLock(-1);
    }
    
    public bool TryEnterReadLock(int millisecondsTimeout)
    {
        return TryEnterReadLock(new TimeoutTracker(millisecondsTimeout));
    }
    
    private bool TryEnterReadLock(TimeoutTracker timeout)
    {
        bool result = false;
        try
        {
            result = TryEnterReadLockCore(timeout);
        }
        finally
        {
        }
        return result;
    }
    
    private bool TryEnterReadLockCore(TimeoutTracker timeout)
    {
        if(fDisposed)
            throw new ObjectDisposedException(null);

        ReaderWriterCount lrwc = null;
        int id = Thread.CurrentThread.ManagedThreadId;

        if (!fIsReentrant)
        {
            if (id == writeLockOwnerId)
            {
                //Check for AW->AR
                throw new LockRecursionException(SR.GetString(SR.LockRecursionException_ReadAfterWriteNotAllowed));
            }
            EnterMyLock();
            lrwc = GetThreadRWCount(false);

            //Check if the reader lock is already acquired. Note, we could check the presence of a reader by not allocating rwc (But that would lead to two lookups in the common case. It's better to keep a count in the struucture).
            if (lrwc.readercount > 0)
            {
                ExitMyLock();
                throw new LockRecursionException(SR.GetString(SR.LockRecursionException_RecursiveReadNotAllowed));
            }
            else if (id == upgradeLockOwnerId)
            {
                //The upgrade lock is already held.Update the global read counts and exit.

                lrwc.readercount++;
                owners++;
                ExitMyLock();
                return true;
            }
        }
        else
        {
            EnterMyLock();
            lrwc = GetThreadRWCount(false);
            if (lrwc.readercount > 0)
            {
                lrwc.readercount++;
                ExitMyLock();
                return true;
            }
            else if (id == upgradeLockOwnerId)
            {
                //The upgrade lock is already held. Update the global read counts and exit.
                lrwc.readercount++;
                owners++;
                ExitMyLock();
                fUpgradeThreadHoldingRead = true;
                return true;
            }
            else if (id == writeLockOwnerId)
            {
                //The write lock is already held.Update global read counts here,
                lrwc.readercount++;
                owners++;
                ExitMyLock();
                return true;
            }
        }

        bool retVal = true;
        int spincount = 0;

        for (; ; )
        {
            // We can enter a read lock if there are only read-locks have been given out and a writer is not trying to get in.  
            if (owners < MAX_READER)
            {
                // Good case, there is no contention, we are basically done
                owners++;       // Indicate we have another reader
                lrwc.readercount++;
                break;
            }

            if (spincount < MaxSpinCount)
            {
                ExitMyLock();
                if (timeout.IsExpired)
                    return false;
                spincount++;
                SpinWait(spincount);
                EnterMyLock();
                //The per-thread structure may have been recycled as the lock is acquired (due to message pumping), load again.
                if(IsRwHashEntryChanged(lrwc))
                    lrwc = GetThreadRWCount(false);
                continue;
            }

            // Drat, we need to wait.  Mark that we have waiters and wait.  
            if (readEvent == null)      // Create the needed event 
            {
                LazyCreateEvent(ref readEvent, false);
                if (IsRwHashEntryChanged(lrwc))
                    lrwc = GetThreadRWCount(false);
                continue;   // since we left the lock, start over. 
            }

            retVal = WaitOnEvent(readEvent, ref numReadWaiters, timeout);
            if (!retVal)
            {
                return false;
            }
            if (IsRwHashEntryChanged(lrwc))
                lrwc = GetThreadRWCount(false);
        }

        ExitMyLock();
        return retVal;
    }
    
    private void EnterMyLock()
    {
        if (Interlocked.CompareExchange(ref myLock, 1, 0) != 0)
            EnterMyLockSpin();
    }

    private void EnterMyLockSpin()
    {
        int pc = Environment.ProcessorCount;
        for (int i = 0; ; i++)
        {
            if (i < LockSpinCount && pc > 1)
            {
                Thread.SpinWait(LockSpinCycles * (i + 1));    // Wait a few dozen instructions to let another processor release lock. 
            }
            else if (i < (LockSpinCount + LockSleep0Count))
            {
                Thread.Sleep(0);        // Give up my quantum.  
            }
            else
            {
                Thread.Sleep(1);        // Give up my quantum.  
            }

            if (myLock == 0 && Interlocked.CompareExchange(ref myLock, 1, 0) == 0)
                return;
        }
    }

    private void ExitMyLock()
    {
        Debug.Assert(myLock != 0, "Exiting spin lock that is not held");
        Volatile.Write(ref myLock, 0);
    }
    /// DontAllocate is set to true if the caller just wants to get an existing entry for this thread, but doesn't want to add one if an existing one could not be found.  
    private ReaderWriterCount GetThreadRWCount(bool dontAllocate)
    {
        ReaderWriterCount rwc = t_rwc;
        ReaderWriterCount empty = null;
        while (rwc != null)
        {
            if (rwc.lockID == this.lockID)
                return rwc;
                
            if (!dontAllocate && empty == null && IsRWEntryEmpty(rwc))
                empty = rwc;

            rwc = rwc.next;
        }
        
        if (dontAllocate)
            return null;

        if (empty == null)
        {
            empty = new ReaderWriterCount();
            empty.next = t_rwc;
            t_rwc = empty;
        }

        empty.lockID = this.lockID;
        return empty;
    }
    
    private static bool IsRWEntryEmpty(ReaderWriterCount rwc)
    {
        if (rwc.lockID == 0)
            return true;
        else if (rwc.readercount == 0 && rwc.writercount == 0 && rwc.upgradecount == 0)
            return true;
        else
            return false;
    }
    
    public void ExitReadLock()
    {
        ReaderWriterCount lrwc = null;
        EnterMyLock();
        lrwc = GetThreadRWCount(true);
        if (lrwc == null || lrwc.readercount < 1)
        {
            //You have to be holding the read lock to make this call.
            ExitMyLock();
            throw new SynchronizationLockException(SR.GetString(SR.SynchronizationLockException_MisMatchedRead));
        }

        if (fIsReentrant)
        {
            if (lrwc.readercount > 1)
            {
                lrwc.readercount--;
                ExitMyLock();
                return;
            }

            if (Thread.CurrentThread.ManagedThreadId == upgradeLockOwnerId)
            {
                fUpgradeThreadHoldingRead = false;
            }
        }
        --owners;
        lrwc.readercount--;
        ExitAndWakeUpAppropriateWaiters();
    }
    
    private void ExitAndWakeUpAppropriateWaiters()
    {
        if (fNoWaiters)
        {
            ExitMyLock();
            return;
        }

        ExitAndWakeUpAppropriateWaitersPreferringWriters();
    }
    
    public void EnterWriteLock()
    {
        TryEnterWriteLock(-1);
    }
    public bool TryEnterWriteLock(int millisecondsTimeout)
    {
        return TryEnterWriteLock(new TimeoutTracker(millisecondsTimeout));
    }
    
    private bool TryEnterWriteLock(TimeoutTracker timeout)
    {
        bool result = false;
        try
        {
            result = TryEnterWriteLockCore(timeout);
        }
        finally
        {
        }
        return result;
    }
    
    private bool TryEnterWriteLockCore(TimeoutTracker timeout)
    {
        if(fDisposed)
            throw new ObjectDisposedException(null); 

        int id = Thread.CurrentThread.ManagedThreadId;
        ReaderWriterCount lrwc;
        bool upgradingToWrite = false;

        if (!fIsReentrant)
        {
            if (id == writeLockOwnerId)
            {
                //Check for AW->AW
                throw new LockRecursionException(SR.GetString(SR.LockRecursionException_RecursiveWriteNotAllowed));
            }
            else if (id == upgradeLockOwnerId)
            {
                //AU->AW case is allowed once.
                upgradingToWrite = true;
            }

            EnterMyLock();
            lrwc = GetThreadRWCount(true);

            //Can't acquire write lock with reader lock held. 
            if (lrwc != null && lrwc.readercount > 0)
            {
                ExitMyLock();
                throw new LockRecursionException(SR.GetString(SR.LockRecursionException_WriteAfterReadNotAllowed));
            }
        }
        else
        {
            EnterMyLock();
            lrwc = GetThreadRWCount(false);
            if (id == writeLockOwnerId)
            {
                lrwc.writercount++;
                ExitMyLock();
                return true;
            }
            else if (id == upgradeLockOwnerId)
            {
                upgradingToWrite = true;
            }
            else if (lrwc.readercount > 0)
            {
                //Write locks may not be acquired if only read locks have been acquired.
                ExitMyLock();
                throw new LockRecursionException(SR.GetString(SR.LockRecursionException_WriteAfterReadNotAllowed));
            }
        }
        int spincount = 0;
        bool retVal = true;
        for (; ; )
        {
            if (IsWriterAcquired())
            {
                // Good case, there is no contention, we are basically done
                SetWriterAcquired();
                break;
            }

            //Check if there is just one upgrader, and no readers.Assumption: Only one thread can have the upgrade lock, so the following check will fail for all other threads that may sneak in when the upgrading thread is waiting.
            
            if (upgradingToWrite)
            {
                uint readercount = GetNumReaders();
                if (readercount == 1)
                {
                    //Good case again, there is just one upgrader, and no readers.
                    SetWriterAcquired();    // indicate we have a writer.
                    break;
                }
                else if (readercount == 2)
                {
                    if (lrwc != null)
                    {
                        if (IsRwHashEntryChanged(lrwc))
                            lrwc = GetThreadRWCount(false);

                        if (lrwc.readercount > 0)
                        {
                            //Good case again, there is just one upgrader, and no readers.
                            SetWriterAcquired();   // indicate we have a writer.
                            break;
                        }                            
                    }
                }
            }

            if (spincount < MaxSpinCount)
            {
                ExitMyLock();
                if (timeout.IsExpired)
                    return false;
                spincount++;
                SpinWait(spincount);
                EnterMyLock();
                continue;
            }

            if (upgradingToWrite)
            {
                if (waitUpgradeEvent == null)   // Create the needed event
                {
                    LazyCreateEvent(ref waitUpgradeEvent, true);
                    continue;   // since we left the lock, start over. 
                }
                retVal = WaitOnEvent(waitUpgradeEvent, ref numWriteUpgradeWaiters, timeout);

                //The lock is not held in case of failure.
                if (!retVal)
                    return false;
            }
            else
            {
                // Drat, we need to wait.  Mark that we have waiters and wait.
                if (writeEvent == null)     // create the needed event.
                {
                    LazyCreateEvent(ref writeEvent, true);
                    continue;   // since we left the lock, start over. 
                }

                retVal = WaitOnEvent(writeEvent, ref numWriteWaiters, timeout);
                //The lock is not held in case of failure.
                if (!retVal)
                    return false;
            }
        }

        if (fIsReentrant)
        {
            if (IsRwHashEntryChanged(lrwc))
                lrwc = GetThreadRWCount(false);
            lrwc.writercount++;
        }

        ExitMyLock();
        writeLockOwnerId = id;
        return true;
    }
    
    public void ExitWriteLock()
    {
        ReaderWriterCount lrwc;
        if (!fIsReentrant)
        {
            if (Thread.CurrentThread.ManagedThreadId != writeLockOwnerId)
            {
                //You have to be holding the write lock to make this call.
                throw new SynchronizationLockException(SR.GetString(SR.SynchronizationLockException_MisMatchedWrite));
            }
            EnterMyLock();
        }
        else
        {
            EnterMyLock();
            lrwc = GetThreadRWCount(false);

            if (lrwc == null)
            {
                ExitMyLock();
                throw new SynchronizationLockException(SR.GetString(SR.SynchronizationLockException_MisMatchedWrite));
            }

            if (lrwc.writercount < 1)
            {
                ExitMyLock();
                throw new SynchronizationLockException(SR.GetString(SR.SynchronizationLockException_MisMatchedWrite));
            }

            lrwc.writercount--;

            if (lrwc.writercount > 0)
            {
                ExitMyLock();
                return;
            }
        }

        ClearWriterAcquired();

        writeLockOwnerId = -1;

        ExitAndWakeUpAppropriateWaiters();
    }
    
    public void EnterUpgradeableReadLock()
    {
        TryEnterUpgradeableReadLock(-1);
    }
    
    private bool TryEnterUpgradeableReadLock(TimeoutTracker timeout)
    {
        bool result = false;
        try
        {
            result = TryEnterUpgradeableReadLockCore(timeout);
        }
        finally
        {
        }
        return result;
    }
        
    private bool TryEnterUpgradeableReadLockCore(TimeoutTracker timeout)
    {
        if(fDisposed)
            throw new ObjectDisposedException(null); 

        int id = Thread.CurrentThread.ManagedThreadId;
        ReaderWriterCount lrwc;

        if (!fIsReentrant)
        {
            if (id == upgradeLockOwnerId)
            {
                //Check for AU->AU
                throw new LockRecursionException(SR.GetString(SR.LockRecursionException_RecursiveUpgradeNotAllowed));
            }
            else if (id == writeLockOwnerId)
            {
                //Check for AU->AW
                throw new LockRecursionException(SR.GetString(SR.LockRecursionException_UpgradeAfterWriteNotAllowed));
            }
            
            EnterMyLock();
            lrwc = GetThreadRWCount(true);
            //Can't acquire upgrade lock with reader lock held. 
            if (lrwc != null && lrwc.readercount > 0)
            {
                ExitMyLock();
                throw new LockRecursionException(SR.GetString(SR.LockRecursionException_UpgradeAfterReadNotAllowed));
            }
        }
        else
        {
            EnterMyLock();
            lrwc = GetThreadRWCount(false);

            if (id == upgradeLockOwnerId)
            {
                lrwc.upgradecount++;
                ExitMyLock();
                return true;
            }
            else if (id == writeLockOwnerId)
            {
                //Write lock is already held, Just update the global state to show presence of upgrader.
                Debug.Assert((owners & WRITER_HELD) > 0);
                owners++;
                upgradeLockOwnerId = id;
                lrwc.upgradecount++;
                if (lrwc.readercount > 0)
                    fUpgradeThreadHoldingRead = true;
                ExitMyLock();                    
                return true;
            }
            else if (lrwc.readercount > 0)
            {
                //Upgrade locks may not be acquired if only read locks have been acquired.                
                ExitMyLock();
                throw new LockRecursionException(SR.GetString(SR.LockRecursionException_UpgradeAfterReadNotAllowed));
            }
        }

        bool retVal = true;
        int spincount = 0;

        for (; ; )
        {
            //Once an upgrade lock is taken, it's like having a reader lock held
            //until upgrade or downgrade operations are performed.              

            if ((upgradeLockOwnerId == -1) && (owners < MAX_READER))
            {
                owners++;
                upgradeLockOwnerId = id;
                break;
            }

            if (spincount < MaxSpinCount)
            {
                ExitMyLock();
                if (timeout.IsExpired)
                    return false;
                spincount++;
                SpinWait(spincount);
                EnterMyLock();
                continue;
            }

            // Drat, we need to wait.  Mark that we have waiters and wait. 
            if (upgradeEvent == null)   // Create the needed event
            {
                LazyCreateEvent(ref upgradeEvent, true);
                continue;   // since we left the lock, start over. 
            }

            //Only one thread with the upgrade lock held can proceed.
            retVal = WaitOnEvent(upgradeEvent, ref numUpgradeWaiters, timeout);
            if (!retVal)
                return false;
        }

        if (fIsReentrant)
        {
            //The lock may have been dropped getting here, so make a quick check to see whether some other
            //thread did not grab the entry.
            if (IsRwHashEntryChanged(lrwc))
                lrwc = GetThreadRWCount(false);
            lrwc.upgradecount++;
        }

        ExitMyLock();
        return true;
    }
    
    public void ExitUpgradeableReadLock()
    {
        ReaderWriterCount lrwc;
        if (!fIsReentrant)
        {
            if (Thread.CurrentThread.ManagedThreadId != upgradeLockOwnerId)
            {
                //You have to be holding the upgrade lock to make this call.
                throw new SynchronizationLockException(SR.GetString(SR.SynchronizationLockException_MisMatchedUpgrade));
            }
            EnterMyLock();
        }
        else
        {
            EnterMyLock();
            lrwc = GetThreadRWCount(true);

            if (lrwc == null)
            {
                ExitMyLock();
                throw new SynchronizationLockException(SR.GetString(SR.SynchronizationLockException_MisMatchedUpgrade));
            }

            if (lrwc.upgradecount < 1)
            {
                ExitMyLock();
                throw new SynchronizationLockException(SR.GetString(SR.SynchronizationLockException_MisMatchedUpgrade));
            }

            lrwc.upgradecount--;

            if (lrwc.upgradecount > 0)
            {
                ExitMyLock();
                return;
            }
            fUpgradeThreadHoldingRead = false;
        }

        owners--;
        upgradeLockOwnerId = -1;

        ExitAndWakeUpAppropriateWaiters();
    }
    
    private void ExitAndWakeUpAppropriateWaitersPreferringWriters()
    {
        bool setUpgradeEvent = false;
        bool setReadEvent = false;
        uint readercount = GetNumReaders();

        //We need this case for EU->ER->EW case, as the read count will be 2 in that scenario.
        if (fIsReentrant)
        {
            if (numWriteUpgradeWaiters > 0 && fUpgradeThreadHoldingRead && readercount == 2)
            {
                ExitMyLock();          // Exit before signaling to improve efficiency (wakee will need the lock)
                waitUpgradeEvent.Set();     // release all upgraders (however there can be at most one). 
                return;
            }
        }

        if (readercount == 1 && numWriteUpgradeWaiters > 0)
        {
            //We have to be careful now, as we are droppping the lock. No new writes should be allowed to sneak in if an upgrade was pending. 
            ExitMyLock();          // Exit before signaling to improve efficiency (wakee will need the lock)
            waitUpgradeEvent.Set();     // release all upgraders (however there can be at most one).            
        }
        else if (readercount == 0 && numWriteWaiters > 0)
        {
            ExitMyLock();      // Exit before signaling to improve efficiency (wakee will need the lock)
            writeEvent.Set();   // release one writer. 
        }
        else if (readercount >= 0)
        {
            if (numReadWaiters != 0 || numUpgradeWaiters != 0)
            {
                if (numReadWaiters != 0)
                    setReadEvent = true;

                if (numUpgradeWaiters != 0 && upgradeLockOwnerId == -1)
                {
                    setUpgradeEvent = true;
                }

                ExitMyLock();    // Exit before signaling to improve efficiency (wakee will need the lock)

                if (setReadEvent)
                    readEvent.Set();  // release all readers. 

                if (setUpgradeEvent)
                    upgradeEvent.Set(); //release one upgrader.
            }
            else
                ExitMyLock();
        }
        else
            ExitMyLock();
    }
    
    private void LazyCreateEvent(ref EventWaitHandle waitEvent, bool makeAutoResetEvent)
    {
        ExitMyLock();
        EventWaitHandle newEvent;
        if (makeAutoResetEvent)
            newEvent = new AutoResetEvent(false);
        else
            newEvent = new ManualResetEvent(false);
        EnterMyLock();
        if (waitEvent == null)          // maybe someone snuck in. 
            waitEvent = newEvent;
        else
            newEvent.Close();
    }
    
    private bool IsRwHashEntryChanged(ReaderWriterCount lrwc)
    {
        return lrwc.lockID != this.lockID;
    }
    
    private bool IsWriterAcquired()
    {
        return (owners & ~WAITING_WRITERS) == 0;
    }    
    private void SetWriterAcquired()
    {
        owners |= WRITER_HELD;    // indicate we have a writer.
    }
    
    private bool WaitOnEvent(EventWaitHandle waitEvent, ref uint numWaiters, TimeoutTracker timeout)
    {
        waitEvent.Reset();
        numWaiters++;
        fNoWaiters = false;

        //Setting these bits will prevent new readers from getting in.
        if (numWriteWaiters == 1)
            SetWritersWaiting();
        if (numWriteUpgradeWaiters == 1)
            SetUpgraderWaiting();            

        bool waitSuccessful = false;
        ExitMyLock();      // Do the wait outside of any lock

        try
        {
            waitSuccessful = waitEvent.WaitOne(timeout.RemainingMilliseconds);
        }
        finally
        {
            EnterMyLock();
            --numWaiters;

            if (numWriteWaiters == 0 && numWriteUpgradeWaiters == 0 && numUpgradeWaiters == 0 && numReadWaiters == 0)
                fNoWaiters = true;

            if (numWriteWaiters == 0)
                ClearWritersWaiting();
            if (numWriteUpgradeWaiters == 0)
                ClearUpgraderWaiting();
            
            if (!waitSuccessful)        // We may also be aboutto throw for some reason.  Exit myLock. 
                ExitMyLock();
        }
        return waitSuccessful;
    }
    
    private uint GetNumReaders()
    {
        return owners & READER_MASK;
    }
    
    public int CurrentReadCount
    {
        get
        {
            int numreaders = (int)GetNumReaders();

            if (upgradeLockOwnerId != -1)
                return numreaders - 1;
            else
                return numreaders;
        }
    }
    private struct TimeoutTracker
    {
        private int m_total;
        private int m_start;
        public TimeoutTracker(TimeSpan timeout)
        {
            long ltm = (long)timeout.TotalMilliseconds;
            if (ltm < -1 || ltm > (long)Int32.MaxValue)
                throw new ArgumentOutOfRangeException("timeout");
            m_total = (int)ltm;
            if (m_total != -1 && m_total != 0)
                m_start = Environment.TickCount;
            else
                m_start = 0;
        }

        public TimeoutTracker(int millisecondsTimeout)
        {
            if (millisecondsTimeout < -1)
                throw new ArgumentOutOfRangeException("millisecondsTimeout");
            m_total = millisecondsTimeout;
            if (m_total != -1 && m_total != 0)
                m_start = Environment.TickCount;
            else
                m_start = 0;
        }

        public int RemainingMilliseconds
        {
            get
            {
                if (m_total == -1 || m_total == 0)
                    return m_total;

                int elapsed = Environment.TickCount - m_start;
                // elapsed may be negative if TickCount has overflowed by 2^31 milliseconds.
                if (elapsed < 0 || elapsed >= m_total)
                    return 0;
                return m_total - elapsed;
            }
        }

        public bool IsExpired
        {
            get
            {
                return RemainingMilliseconds == 0;
            }
        }
    }
}

ReaderWriterLockSlim的默认够着函数是不支持递归的,EnterReadLock的核心方法是TryEnterReadLockCore,TryEnterReadLockCore方法的实现也比较简单,首先进入自己的锁,然后获取自己ReaderWriterCount实例,因为一般情况下我们的reader都不会到达上限,所以直接 把ReaderWriterCount的readercount加1,owners++; 然后退出锁,返回成功,如果是递归调用就要复杂一点了。ExitReadLock也是非常简单,一样是进入自己的锁,找到ReaderWriterCount实例,执行--owners;lrwc.readercount--; 在退出自己的锁,到这里我们知道共享锁的一个实现实例。

EnterWriteLock 的实现核心是TryEnterWriteLockCore方法,TryEnterWriteLockCore方法因为只有一个线程可以进入,所以有点不一样,不考虑递归也是先进入锁的模式,查找ReaderWriterCount实例,然后执行if (IsWriterAcquired()){SetWriterAcquired();break;}【当前申请读锁成功,一般是没有这个好的情况的哦】,实际更多情况都会执行到retVal = WaitOnEvent(writeEvent, ref numWriteWaiters, timeout);等待锁,最后记录锁的id ,writeLockOwnerId = id; ExitWriteLock的实现 writeLockOwnerId = -1,然后在ExitAndWakeUpAppropriateWaiters

EnterUpgradeableReadLock的实现核心是TryEnterUpgradeableReadLockCore方法,也是进入锁模式,获取ReaderWriterCount实例,如果当前没有升级锁则 执行【owners++;upgradeLockOwnerId = id;】,否者就只有等待了retVal = WaitOnEvent(upgradeEvent, ref numUpgradeWaiters, timeout);  ExitUpgradeableReadLock的实现【 owners--;upgradeLockOwnerId = -1;】最后调用ExitAndWakeUpAppropriateWaiters, ExitAndWakeUpAppropriateWaiters这个方法就是检查是否有锁与需要重置信号。

 在这里也实现了不用lock来实现lock的效果:

  int myLock;
  private void EnterMyLock()
    {
        if (Interlocked.CompareExchange(ref myLock, 1, 0) != 0)
            EnterMyLockSpin();
    }

    private void EnterMyLockSpin()
    {
        int pc = Environment.ProcessorCount;
        for (int i = 0; ; i++)
        {
            if (i < LockSpinCount && pc > 1)
            {
                Thread.SpinWait(LockSpinCycles * (i + 1));    // Wait a few dozen instructions to let another processor release lock. 
            }
            else if (i < (LockSpinCount + LockSleep0Count))
            {
                Thread.Sleep(0);        // Give up my quantum.  
            }
            else
            {
                Thread.Sleep(1);        // Give up my quantum.  
            }

            if (myLock == 0 && Interlocked.CompareExchange(ref myLock, 1, 0) == 0)
                return;
        }
    }
    private void ExitMyLock()
    {
        Debug.Assert(myLock != 0, "Exiting spin lock that is not held");
        Volatile.Write(ref myLock, 0);
    }

 

posted on 2017-12-28 09:49  dz45693  阅读(4542)  评论(1编辑  收藏  举报

导航