//****************************************************************************************************** // ReaderWriterSpinLock.cs - Gbtc // // Copyright © 2012, Grid Protection Alliance. All Rights Reserved. // // Licensed to the Grid Protection Alliance (GPA) under one or more contributor license agreements. See // the NOTICE file distributed with this work for additional information regarding copyright ownership. // The GPA licenses this file to you under the Eclipse Public License -v 1.0 (the "License"); you may // not use this file except in compliance with the License. You may obtain a copy of the License at: // // http://www.opensource.org/licenses/eclipse-1.0.php // // Unless agreed to in writing, the subject software distributed under the License is distributed on an // "AS-IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. Refer to the // License for the specific language governing permissions and limitations. // // Code Modification History: // ---------------------------------------------------------------------------------------------------- // 03/21/2011 - Ritchie // Generated original version of source code based on Joe Duffy's Weblog articles: // "A single-word reader/writer spin lock", January 29, 2009 // http://www.bluebytesoftware.com/blog/2009/01/30/ASinglewordReaderwriterSpinLock.aspx // "A more scalable reader/writer lock, and a bit less harsh consideration of the idea", February 20, 2009 // http://www.bluebytesoftware.com/blog/2009/02/21/AMoreScalableReaderwriterLockAndABitLessHarshConsiderationOfTheIdea.aspx // 12/14/2012 - Starlynn Danyelle Gilliam // Modified Header. // //****************************************************************************************************** using System; using System.Runtime.InteropServices; using System.Threading; // We use plenty of interlocked operations on volatile fields below. Safe. #pragma warning disable 0420 namespace GSF.Threading { /// /// Represents a fast, lightweight reader/writer lock that uses spinning to perform locking with no thread affinity. No recursive /// acquires or upgradable locks are allowed (i.e., all entered locks must be exited before entering another lock). /// /// /// /// uses a single word of memory and only spins when contention arises so no events are necessary. /// /// /// This reader/writer lock uses to spin the CPU instead of engaging event based locking. As a result it /// should only be used in cases where lock times are expected to be very small, reads are very frequent and writes are rare. /// If hold times for write locks can be lengthy, it will be better to use instead to avoid /// unnecessary CPU utilization due to spinning incurred by waiting reads. /// /// public class ReaderWriterSpinLock { #region [ Members ] // Nested Types [StructLayout(LayoutKind.Sequential, Size = 128)] struct ReadEntry { internal volatile int m_taken; } // Fields private volatile int m_writer; private volatile ReadEntry[] m_readers = new ReadEntry[Environment.ProcessorCount * 16]; #endregion #region [ Properties ] private int ReadLockIndex { get { return Thread.CurrentThread.ManagedThreadId % m_readers.Length; } } #endregion #region [ Methods ] /// /// Enters the lock in write mode. /// /// /// Upon successful aquistion of a write lock, use the finally block of a try/finally statement to call . /// One should be called for each . /// public void EnterWriteLock() { SpinWait sw = new SpinWait(); while (true) { if (m_writer == 0 && Interlocked.Exchange(ref m_writer, 1) == 0) { // We now hold the write lock, and prevent new readers - // but we must ensure no readers exist before proceeding. for (int i = 0; i < m_readers.Length; i++) { while (m_readers[i].m_taken != 0) sw.SpinOnce(); } break; } // We failed to take the write lock; wait a bit and retry. sw.SpinOnce(); } } /// /// Exits write mode. /// public void ExitWriteLock() { // No need for a CAS. m_writer = 0; } /// /// Enters the lock in read mode. /// /// /// Upon successful aquistion of a read lock, use the finally block of a try/finally statement to call . /// One should be called for each . /// public void EnterReadLock() { SpinWait sw = new SpinWait(); int tid = ReadLockIndex; // Wait until there are no writers. while (true) { while (m_writer == 1) sw.SpinOnce(); // Try to take the read lock. Interlocked.Increment(ref m_readers[tid].m_taken); if (m_writer == 0) { // Success, no writer, proceed. break; } // Back off, to let the writer go through. Interlocked.Decrement(ref m_readers[tid].m_taken); } } /// /// Exits read mode. /// /// Cannot exit read lock when there are no readers. public void ExitReadLock() { // Just note that the current reader has left the lock. Interlocked.Decrement(ref m_readers[ReadLockIndex].m_taken); } #endregion } }