//******************************************************************************************************
// 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
}
}