IntroductionMaintaining the state of shared object/variable is very important because although one can make his application as multi-threaded as he likes, sometime the threads has to come together to share either the result of their computation with the main thread or some other information necessary for the healthy functioning of the application. So in this post, I am going to talk about SpinLock and how it can be used in C# in other to maintain state in a mult-threaded/concurrent application.
So what is SpinLock all about?
[ComVisibleAttribute(false)] [HostProtectionAttribute(SecurityAction.LinkDemand, Synchronization = true, ExternalThreading = true)] public struct SpinLock
If you ask MSDN, they will tell you that SpinLock is a lock that "provides a mutual exclusion lock primitive where a thread trying to acquire the lock waits in a loop repeatedly checking until the lock becomes available." what this statement means is that SpinLock is just like an ordinary lock except that it is a non-blocking lock that functions by continually spinning a thread that wants to acquire a lock until the lock becomes available. What this entails is that SpinLock is a CPU intensive locking scheme that prefers to burn its allocated time slice by continually spinning and doing nothing except checking if the lock had been released. Now another thing about SpinLock is that it is a struct and therefore should not be passed around except with the use of the ref keyword. Furthermore SpinLocks are non re-entrant, this means that it should not be called twice by the thread that has already acquired the lock except when you want to deadlock your application which I bet is never. On the good side though SpinLock minimizes context switching unlike its relatives (lock and co) seems to aide context switching. Therefore it relatively improves performance.
Declaration, Properties and Methods
SpinLock has a parameterless constructor which can be instantiated as shown below:
var locker = new SpinLock();
Now a lock can be acquired by a thread when it calls SpinLocks' instance Enter() or TryEnter() equivalent (Remember this method takes in a timeout) method. When the Enter() method is called, it should be passed a boolean variable with default set to false. This variable should be passed with the ref keyword (ie: passed as a reference). This variable usually indicates whether the current thread succeeded in taking the lock. The Enter() method should be optimally called inside a try block, so that when an exception occurs, the finally block will be called and SpinLocks' Exit() instance method called depending on whether the lock was previously acquired.
public class StateModifier
private SpinLock _locker = new SpinLock();
bool lockTaken = false;
int sharedState = 0;
public void Increment()
Thread.Sleep(100); //To simulate short executing action
The above class tries to show you how a SpinLock can be acquired and released inside the try/finally block statement and the use of lockTaken boolean variable passed to the Enter() by reference. Furthermore, the SpinLock instance has other properties like:
- IsHeld: Used to check whether the lock is currently being held by any thread.
- IsHeldByCurrentThread: This property is used to check whether the lock is being held by the currently executing thread.
- IsThreadOwerTrackingEnabled: This property is used to check whether the thread that is currently executng the shared state is being tracked (Rememeber that SpinLock is non re-entrant so a thread that is currently being tracked will throw a LockRecursionException when the Enter() method is called twice).
Dos and Donts of SpinLock.
- Use SpinLock when you want to write non-blocking shared state management code.
- You can use SpinLock when executing a very short code inside the shared state.
- When there are many competing threads.
- Do not copy SpinLock as doing this will cause an unwanted behaviour in your code.
- Always pass the SpinLock instance around using the ref keyword so as not duplicate it (Remember its a struct)
- Avoid allocating much memory.
- Do not make a statically dispatched call into any code that you do not own.
- Avoid making dynamically dispatched calls (by the use of interfaces and calling of virtual methods)
Though SpinLock does limit concurrency, always try as much as possible to lessen its use (unless you want to needlessly burn your CPU circles for no just cause) because it has so many limitation and nuisances. Also its use is seriously discouraged by Microsoft. On the brighter side though you can use SpinLock when you want to implement your own reusable synchronization constructs.