I'm trying to understand the logic behind how this class was written, and when I should and shouldn't use it. Any insight would be appreciated
internal struct SpinLock
{
private volatile int lockHeld;
private readonly static int processorCount;
public bool IsHeld
{
get
{
return this.lockHeld != 0;
}
}
static SpinLock()
{
SpinLock.processorCount = Environment.ProcessorCount;
}
public void Enter()
{
if (Interlocked.CompareExchange(ref this.lockHeld, 1, 0) != 0)
{
this.EnterSpin();
}
}
private void EnterSpin()
{
int num = 0;
while (this.lockHeld != null || Interlocked.CompareExchange(ref this.lockHeld, 1, 0) != 0)
{
if (num >= 20 || SpinLock.processorCount <= 1)
{
if (num >= 25)
{
Thread.Sleep(1);
}
else
{
Thread.Sleep(0);
}
}
else
{
Thread.SpinWait(100);
}
num++;
}
}
public void Exit()
{
this.lockHeld = 0;
}
}
Update: I found a sample usage in my source code... This indicates how to use the above object, though I don't understand "why"
internal class FastReaderWriterLock
{
private SpinLock myLock;
private uint numReadWaiters;
private uint numWriteWaiters;
private int owners;
private EventWaitHandle readEvent;
private EventWaitHandle writeEvent;
public FastReaderWriterLock()
{
}
public void AcquireReaderLock(int millisecondsTimeout)
{
this.myLock.Enter();
while (this.owners < 0 || this.numWriteWaiters != 0)
{
if (this.readEvent != null)
{
this.WaitOnEvent(this.readEvent, ref this.numReadWaiters, millisecondsTimeout);
}
else
{
this.LazyCreateEvent(ref this.readEvent, false);
}
}
FastReaderWriterLock fastReaderWriterLock = this;
fastReaderWriterLock.owners = fastReaderWriterLock.owners + 1;
this.myLock.Exit();
}
private void WaitOnEvent(EventWaitHandle waitEvent, ref uint numWaiters, int millisecondsTimeout)
{
waitEvent.Reset();
uint& numPointer = numWaiters;
bool flag = false;
this.myLock.Exit();
try
{
if (waitEvent.WaitOne(millisecondsTimeout, false))
{
flag = true;
}
else
{
throw new TimeoutException("ReaderWriterLock timeout expired");
}
}
finally
{
this.myLock.Enter();
uint& numPointer1 = numWaiters;
if (!flag)
{
this.myLock.Exit();
}
}
}
}
SpinLocks in general are a form of lock that keeps waiting threads awake (cycling over and over on a check condition in a tight loop- think "Mommy are we there yet?") rather than relying on heavier, slower, kernel mode signals. They are usually intended for situations where the expected wait time is very short, where they outperform the overhead of creating and waiting on an OS handle for a traditional lock. They incur more CPU cost than a traditional lock though, so for more than a very short wait time a traditional lock (like the Monitor class or the various WaitHandle implementations) is preferred.
This short wait time concept is demonstrated in your code above:
waitEvent.Reset();
// All that we are doing here is setting some variables.
// It has to be atomic, but it's going to be *really* fast
uint& numPointer = numWaiters;
bool flag = false;
// And we are done. No need for an OS wait handle for 2 lines of code.
this.myLock.Exit();
There is a perfectly good SpinLock built into the BCL, however it is only in v4.0+, so if you are working in an older version of the .NET framework or on code that was migrated from an older version, someone may have written their own implementation.
To answer your question: You should use the built-in SpinLock if you are writing new code on .NET 4.0 or higher. For code on 3.5 or older, especially if you are extending Nesper, I'd argue that this implementation is time-tested and appropriate. Only use a SpinLock where you know that the time a thread may wait on it is very small, as in the example above.
EDIT: Looks like your implementation came from Nesper- the .NET port of the Esper CEP library:
https://svn.codehaus.org/esper/esper/tagsnet/release_1.12.0_beta_1/trunk/NEsper/compat/SpinLock.cs
and
I can confirm that Nesper existed long before the .NET framework 4, so that explains the need for a home-spun SpinLock.