Note

Access to this page requires authorization. You can try signing in or .

Access to this page requires authorization. You can try .

SpinLock Struct

Definition

Namespace:
System.Threading
Assemblies:
mscorlib.dll, System.Threading.dll
Assemblies:
netstandard.dll, System.Threading.dll
Assembly:
System.Threading.dll
Assembly:
mscorlib.dll
Assembly:
netstandard.dll
Source:
SpinLock.cs
Source:
SpinLock.cs
Source:
SpinLock.cs
Source:
SpinLock.cs
Source:
SpinLock.cs

Important

Some information relates to prerelease product that may be substantially modified before it’s released. Microsoft makes no warranties, express or implied, with respect to the information provided here.

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.

public value class SpinLock
public struct SpinLock
[System.Runtime.InteropServices.ComVisible(false)]
public struct SpinLock
type SpinLock = struct
[<System.Runtime.InteropServices.ComVisible(false)>]
type SpinLock = struct
Public Structure SpinLock
Inheritance
SpinLock
Attributes

Examples

The following example shows how to use a SpinLock:

using System;
using System.Linq;
using System.Text;
using System.Threading;
using System.Threading.Tasks;

class SpinLockDemo
{

 // Demonstrates:
 // Default SpinLock construction ()
 // SpinLock.Enter(ref bool)
 // SpinLock.Exit()
 static void SpinLockSample1()
 {
 SpinLock sl = new SpinLock();

 StringBuilder sb = new StringBuilder();

 // Action taken by each parallel job.
 // Append to the StringBuilder 10000 times, protecting
 // access to sb with a SpinLock.
 Action action = () =>
 {
 bool gotLock = false;
 for (int i = 0; i < 10000; i++)
 {
 gotLock = false;
 try
 {
 sl.Enter(ref gotLock);
 sb.Append((i % 10).ToString());
 }
 finally
 {
 // Only give up the lock if you actually acquired it
 if (gotLock) sl.Exit();
 }
 }
 };

 // Invoke 3 concurrent instances of the action above
 Parallel.Invoke(action, action, action);

 // Check/Show the results
 Console.WriteLine("sb.Length = {0} (should be 30000)", sb.Length);
 Console.WriteLine("number of occurrences of '5' in sb: {0} (should be 3000)",
 sb.ToString().Where(c => (c == '5')).Count());
 }

 // Demonstrates:
 // Default SpinLock constructor (tracking thread owner)
 // SpinLock.Enter(ref bool)
 // SpinLock.Exit() throwing exception
 // SpinLock.IsHeld
 // SpinLock.IsHeldByCurrentThread
 // SpinLock.IsThreadOwnerTrackingEnabled
 static void SpinLockSample2()
 {
 // Instantiate a SpinLock
 SpinLock sl = new SpinLock();

 // These MRESs help to sequence the two jobs below
 ManualResetEventSlim mre1 = new ManualResetEventSlim(false);
 ManualResetEventSlim mre2 = new ManualResetEventSlim(false);
 bool lockTaken = false;

 Task taskA = Task.Factory.StartNew(() =>
 {
 try
 {
 sl.Enter(ref lockTaken);
 Console.WriteLine("Task A: entered SpinLock");
 mre1.Set(); // Signal Task B to commence with its logic

 // Wait for Task B to complete its logic
 // (Normally, you would not want to perform such a potentially
 // heavyweight operation while holding a SpinLock, but we do it
 // here to more effectively show off SpinLock properties in
 // taskB.)
 mre2.Wait();
 }
 finally
 {
 if (lockTaken) sl.Exit();
 }
 });

 Task taskB = Task.Factory.StartNew(() =>
 {
 mre1.Wait(); // wait for Task A to signal me
 Console.WriteLine("Task B: sl.IsHeld = {0} (should be true)", sl.IsHeld);
 Console.WriteLine("Task B: sl.IsHeldByCurrentThread = {0} (should be false)", sl.IsHeldByCurrentThread);
 Console.WriteLine("Task B: sl.IsThreadOwnerTrackingEnabled = {0} (should be true)", sl.IsThreadOwnerTrackingEnabled);

 try
 {
 sl.Exit();
 Console.WriteLine("Task B: Released sl, should not have been able to!");
 }
 catch (Exception e)
 {
 Console.WriteLine("Task B: sl.Exit resulted in exception, as expected: {0}", e.Message);
 }

 mre2.Set(); // Signal Task A to exit the SpinLock
 });

 // Wait for task completion and clean up
 Task.WaitAll(taskA, taskB);
 mre1.Dispose();
 mre2.Dispose();
 }

 // Demonstrates:
 // SpinLock constructor(false) -- thread ownership not tracked
 static void SpinLockSample3()
 {
 // Create SpinLock that does not track ownership/threadIDs
 SpinLock sl = new SpinLock(false);

 // Used to synchronize with the Task below
 ManualResetEventSlim mres = new ManualResetEventSlim(false);

 // We will verify that the Task below runs on a separate thread
 Console.WriteLine("main thread id = {0}", Thread.CurrentThread.ManagedThreadId);

 // Now enter the SpinLock. Ordinarily, you would not want to spend so
 // much time holding a SpinLock, but we do it here for the purpose of 
 // demonstrating that a non-ownership-tracking SpinLock can be exited 
 // by a different thread than that which was used to enter it.
 bool lockTaken = false;
 sl.Enter(ref lockTaken);

 // Create a separate Task from which to Exit() the SpinLock
 Task worker = Task.Factory.StartNew(() =>
 {
 Console.WriteLine("worker task thread id = {0} (should be different than main thread id)",
 Thread.CurrentThread.ManagedThreadId);

 // Now exit the SpinLock
 try
 {
 sl.Exit();
 Console.WriteLine("worker task: successfully exited SpinLock, as expected");
 }
 catch (Exception e)
 {
 Console.WriteLine("worker task: unexpected failure in exiting SpinLock: {0}", e.Message);
 }

 // Notify main thread to continue
 mres.Set();
 });

 // Do this instead of worker.Wait(), because worker.Wait() could inline the worker Task,
 // causing it to be run on the same thread. The purpose of this example is to show that
 // a different thread can exit the SpinLock created (without thread tracking) on your thread.
 mres.Wait();

 // now Wait() on worker and clean up
 worker.Wait();
 mres.Dispose();
 }
}
Imports System.Text
Imports System.Threading
Imports System.Threading.Tasks


Module SpinLockDemo

 ' Demonstrates:
 ' Default SpinLock construction ()
 ' SpinLock.Enter(ref bool)
 ' SpinLock.Exit()
 Private Sub SpinLockSample1()
 Dim sl As New SpinLock()

 Dim sb As New StringBuilder()

 ' Action taken by each parallel job.
 ' Append to the StringBuilder 10000 times, protecting
 ' access to sb with a SpinLock.
 Dim action As Action =
 Sub()
 Dim gotLock As Boolean = False
 For i As Integer = 0 To 9999
 gotLock = False
 Try
 sl.Enter(gotLock)
 sb.Append((i Mod 10).ToString())
 Finally
 ' Only give up the lock if you actually acquired it
 If gotLock Then
 sl.[Exit]()
 End If
 End Try
 Next
 End Sub

 ' Invoke 3 concurrent instances of the action above
 Parallel.Invoke(action, action, action)

 ' Check/Show the results
 Console.WriteLine("sb.Length = {0} (should be 30000)", sb.Length)
 Console.WriteLine("number of occurrences of '5' in sb: {0} (should be 3000)", sb.ToString().Where(Function(c) (c = "5"c)).Count())
 End Sub

 ' Demonstrates:
 ' Default SpinLock constructor (tracking thread owner)
 ' SpinLock.Enter(ref bool)
 ' SpinLock.Exit() throwing exception
 ' SpinLock.IsHeld
 ' SpinLock.IsHeldByCurrentThread
 ' SpinLock.IsThreadOwnerTrackingEnabled
 Private Sub SpinLockSample2()
 ' Instantiate a SpinLock
 Dim sl As New SpinLock()

 ' These MRESs help to sequence the two jobs below
 Dim mre1 As New ManualResetEventSlim(False)
 Dim mre2 As New ManualResetEventSlim(False)
 Dim lockTaken As Boolean = False

 Dim taskA As Task = Task.Factory.StartNew(
 Sub()
 Try
 sl.Enter(lockTaken)
 Console.WriteLine("Task A: entered SpinLock")
 mre1.[Set]()
 ' Signal Task B to commence with its logic
 ' Wait for Task B to complete its logic
 ' (Normally, you would not want to perform such a potentially
 ' heavyweight operation while holding a SpinLock, but we do it
 ' here to more effectively show off SpinLock properties in
 ' taskB.)
 mre2.Wait()
 Finally
 If lockTaken Then
 sl.[Exit]()
 End If
 End Try
 End Sub)

 Dim taskB As Task = Task.Factory.StartNew(
 Sub()
 mre1.Wait()
 ' wait for Task A to signal me
 Console.WriteLine("Task B: sl.IsHeld = {0} (should be true)", sl.IsHeld)
 Console.WriteLine("Task B: sl.IsHeldByCurrentThread = {0} (should be false)", sl.IsHeldByCurrentThread)
 Console.WriteLine("Task B: sl.IsThreadOwnerTrackingEnabled = {0} (should be true)", sl.IsThreadOwnerTrackingEnabled)

 Try
 sl.[Exit]()
 Console.WriteLine("Task B: Released sl, should not have been able to!")
 Catch e As Exception
 Console.WriteLine("Task B: sl.Exit resulted in exception, as expected: {0}", e.Message)
 End Try

 ' Signal Task A to exit the SpinLock
 mre2.[Set]()
 End Sub)

 ' Wait for task completion and clean up
 Task.WaitAll(taskA, taskB)
 mre1.Dispose()
 mre2.Dispose()
 End Sub

 ' Demonstrates:
 ' SpinLock constructor(false) -- thread ownership not tracked
 Private Sub SpinLockSample3()
 ' Create SpinLock that does not track ownership/threadIDs
 Dim sl As New SpinLock(False)

 ' Used to synchronize with the Task below
 Dim mres As New ManualResetEventSlim(False)

 ' We will verify that the Task below runs on a separate thread
 Console.WriteLine("main thread id = {0}", Thread.CurrentThread.ManagedThreadId)

 ' Now enter the SpinLock. Ordinarily, you would not want to spend so
 ' much time holding a SpinLock, but we do it here for the purpose of 
 ' demonstrating that a non-ownership-tracking SpinLock can be exited 
 ' by a different thread than that which was used to enter it.
 Dim lockTaken As Boolean = False
 sl.Enter(lockTaken)

 ' Create a separate Task
 Dim worker As Task = Task.Factory.StartNew(
 Sub()
 Console.WriteLine("worker task thread id = {0} (should be different than main thread id)", Thread.CurrentThread.ManagedThreadId)

 ' Now exit the SpinLock
 Try
 sl.[Exit]()
 Console.WriteLine("worker task: successfully exited SpinLock, as expected")
 Catch e As Exception
 Console.WriteLine("worker task: unexpected failure in exiting SpinLock: {0}", e.Message)
 End Try

 ' Notify main thread to continue
 mres.[Set]()
 End Sub)

 ' Do this instead of worker.Wait(), because worker.Wait() could inline the worker Task,
 ' causing it to be run on the same thread. The purpose of this example is to show that
 ' a different thread can exit the SpinLock created (without thread tracking) on your thread.
 mres.Wait()

 ' now Wait() on worker and clean up
 worker.Wait()
 mres.Dispose()
 End Sub


End Module

Remarks

For an example of how to use a Spin Lock, see How to: Use SpinLock for Low-Level Synchronization.

Spin locks can be used for leaf-level locks where the object allocation implied by using a Monitor, in size or due to garbage collection pressure, is overly expensive. A spin lock can be useful to avoid blocking; however, if you expect a significant amount of blocking, you should probably not use spin locks due to excessive spinning. Spinning can be beneficial when locks are fine-grained and large in number (for example, a lock per node in a linked list) and also when lock hold-times are always extremely short. In general, while holding a spin lock, one should avoid any of these actions:

  • blocking,

  • calling anything that itself may block,

  • holding more than one spin lock at once,

  • making dynamically dispatched calls (interface and virtuals),

  • making statically dispatched calls into any code one doesn't own, or

  • allocating memory.

SpinLock should only be used after you have been determined that doing so will improve an application's performance. It is also important to note that SpinLock is a value type, for performance reasons. For this reason, you must be very careful not to accidentally copy a SpinLock instance, as the two instances (the original and the copy) would then be completely independent of one another, which would likely lead to erroneous behavior of the application. If a SpinLock instance must be passed around, it should be passed by reference rather than by value.

Do not store SpinLock instances in readonly fields.

Constructors

Name Description
SpinLock(Boolean)

Initializes a new instance of the SpinLock structure with the option to track thread IDs to improve debugging.

Properties

Name Description
IsHeld

Gets whether the lock is currently held by any thread.

IsHeldByCurrentThread

Gets whether the lock is held by the current thread.

IsThreadOwnerTrackingEnabled

Gets whether thread ownership tracking is enabled for this instance.

Methods

Name Description
Enter(Boolean)

Acquires the lock in a reliable manner, such that even if an exception occurs within the method call, lockTaken can be examined reliably to determine whether the lock was acquired.

Exit()

Releases the lock.

Exit(Boolean)

Releases the lock.

TryEnter(Boolean)

Attempts to acquire the lock in a reliable manner, such that even if an exception occurs within the method call, lockTaken can be examined reliably to determine whether the lock was acquired.

TryEnter(Int32, Boolean)

Attempts to acquire the lock in a reliable manner, such that even if an exception occurs within the method call, lockTaken can be examined reliably to determine whether the lock was acquired.

TryEnter(TimeSpan, Boolean)

Attempts to acquire the lock in a reliable manner, such that even if an exception occurs within the method call, lockTaken can be examined reliably to determine whether the lock was acquired.

Applies to

Thread Safety

All members of SpinLock are thread-safe and may be used from multiple threads concurrently.

See also


Feedback

Was this page helpful?