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.
It appears that the original author wanted a faster version of ReaderWriterLock
. This old class was painfully slow. My own tests (which I did a long time ago) indicates that RWL had ~8x the overhead of a plain old lock
. ReaderWriterLockSlim
improved things quite a bit (though it still has ~2x the overhead as compared to lock
). At this point I would say ditch the custom code and just use the newer ReaderWriterLockSlim
class.
But, for what it is worth let me explain some of that custom SpinLock
code.
Interlocked.CompareExchange
is .NET's version of a CAS operation. It is the most fundamental synchronization primitive. You can literally build everything else from this single operation including your own customMonitor
-like class, reader writer locks, etc. Obviously it was used here to create a spin lock.Thread.Sleep(0)
yields to any thread of with the same or higher priority on any processor.Thread.Sleep(1)
yields to any thread on any processor.Thread.SpinWait
puts the thread into a tight loop for the specified number of iterations.
Although it was not used in the code you posted there is another useful mechanism for creating spin locks (or other low lock strategies).
Thread.Yield
yields to any thread on the same processor.
Microsoft uses all of these calls in their highly concurrent synchronization mechanisms and collections. If you decompile SpinLock
, SpinWait
, ManualResetEventSlim
, etc. you will see a fairly complex song-and-dance going on with these calls...much more complex than the code you posted.
Again, ditch the custom code and just use ReaderWriterLockSlim
instead of that custom FastReaderWriterLock
class.
By the way, this.lockHeld != null
should produce a compiler warning since lockHeld
is a value type.
来源:https://stackoverflow.com/questions/10509112/when-should-shouldnt-i-use-this-c-sharp-utility-class-to-control-threads-via