SpinLock 结构

定义

提供一个相互排斥锁基元,在该基元中,尝试获取锁的线程将在重复检查的循环中等待,直至该锁变为可用为止。

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
继承
SpinLock
属性

示例

以下示例演示如何使用 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

注解

有关如何使用 Spin Lock 的示例,请参阅 如何:使用 SpinLock 进行Low-Level同步

旋转锁可用于叶级锁,其中使用大小或垃圾回收压力隐含 Monitor的对象分配过于昂贵。 旋转锁可用于避免阻塞;但是,如果你期望大量的阻塞,你可能不应该使用旋转锁,因为旋转过多。 当锁细粒度和大量 (时,旋转可能很有用,例如,链接列表中的每个节点的锁) ,并且当锁保持时间始终非常短时。 一般情况下,在按住旋转锁时,应避免以下任何操作:

  • 阻塞

  • 调用本身可能阻止的任何内容,

  • 同时按住多个旋转锁,

  • (接口和虚拟) 进行动态调度调用,

  • 对任何代码不拥有或

  • 分配内存。

SpinLock 只有在确定这样做可以提高应用程序性能后才应使用。 另请务必注意, SpinLock 这是一种值类型,出于性能原因。 因此,必须非常小心,不要意外复制实例,因为两个 SpinLock 实例 (原始实例,然后副本) 将完全独立于另一个实例,这可能会导致应用程序的错误行为。 SpinLock如果必须传递实例,则应通过引用而不是按值传递它。

不要将实例存储在 SpinLock 只读字段中。

构造函数

SpinLock(Boolean)

使用用于跟踪线程 ID 以改善调试的选项初始化 SpinLock 结构的新实例。

属性

IsHeld

获取锁当前是否已由任何线程占用。

IsHeldByCurrentThread

获取锁是否已由当前线程占用。

IsThreadOwnerTrackingEnabled

获取是否已为此实例启用了线程所有权跟踪。

方法

Enter(Boolean)

采用可靠的方式获取锁,这样,即使在方法调用中发生异常的情况下,都能采用可靠的方式检查 lockTaken 以确定是否已获取锁。

Exit()

释放锁。

Exit(Boolean)

释放锁。

TryEnter(Boolean)

尝试采用可靠的方式获取锁,这样,即使在方法调用中发生异常的情况下,都能采用可靠的方式检查 lockTaken 以确定是否已获取锁。

TryEnter(Int32, Boolean)

尝试采用可靠的方式获取锁,这样,即使在方法调用中发生异常的情况下,都能采用可靠的方式检查 lockTaken 以确定是否已获取锁。

TryEnter(TimeSpan, Boolean)

尝试采用可靠的方式获取锁,这样,即使在方法调用中发生异常的情况下,都能采用可靠的方式检查 lockTaken 以确定是否已获取锁。

适用于

线程安全性

所有成员 SpinLock 都是线程安全的,可以同时从多个线程使用。

另请参阅