//****************************************************************************************************** // 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 MIT License (MIT), 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/MIT // // 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 - J. Ritchie Carroll // Generated original version of source code based on Joe Duffy's Weblog articles. // 03/21/2014 - Steven E. Chisholm // Removed the processor fanning that occurred in the readers since the overhead associated // with the reduced contention slowed down the class and each instance of this class consumed // a substantial amount of memory. // //****************************************************************************************************** using System; 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. No recursive acquires or /// upgradable locks are allowed (i.e., all entered locks must be exited before entering another lock). /// /// /// 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 ] // Fields private volatile int m_writer; private volatile int m_readers; #endregion #region [ Properties ] #endregion #region [ Methods ] /// /// Enters the lock in write mode. /// /// /// Upon successful acquisition 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. while (m_readers != 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 acquisition 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(); // 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); // If writer, success - proceed. if (m_writer == 0) break; // Back off to let the writer go through. Interlocked.Decrement(ref m_readers); } } /// /// 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); } #endregion } }