SpinWaitSpinWait

System.Threading.SpinWait 是一种轻型同步类型,可用于低级方案,以避免执行内核事件所需的高成本上下文切换和内核转换。System.Threading.SpinWait is a lightweight synchronization type that you can use in low-level scenarios to avoid the expensive context switches and kernel transitions that are required for kernel events. 在多核计算机上,如果不得长时间保留资源,更高效的做法是,先让等待线程在用户模式下旋转几十或几百个周期,再重试获取资源。On multicore computers, when a resource is not expected to be held for long periods of time, it can be more efficient for a waiting thread to spin in user mode for a few dozen or a few hundred cycles, and then retry to acquire the resource. 如果资源在旋转后可用,便节省了几千个周期。If the resource is available after spinning, then you have saved several thousand cycles. 如果资源仍不可用,那么也只花了几个周期,仍可以进入基于内核的等待。If the resource is still not available, then you have spent only a few cycles and can still enter a kernel-based wait. 这种“旋转后等待”的组合有时称为“两阶段等待操作” 。This spinning-then-waiting combination is sometimes referred to as a two-phase wait operation.

SpinWait 旨在与包装内核事件(如 ManualResetEvent)的 .NET Framework 类型结合使用。SpinWait is designed to be used in conjunction with the .NET Framework types that wrap kernel events such as ManualResetEvent. SpinWait 本身也可以仅在一个程序中用于提供基本的旋转功能。SpinWait can also be used by itself for basic spinning functionality in just one program.

SpinWait 不仅仅只是空循环。SpinWait is more than just an empty loop. 谨慎实现后,它可以提供适用于一般情况的正确旋转行为,并且本身能够在旋转时间够长(大致是内核转换所需的时间长度)时自行启动上下文切换。It is carefully implemented to provide correct spinning behavior for the general case, and will itself initiate context switches if it spins long enough (roughly the length of time required for a kernel transition). 例如,在单核计算机上,SpinWait 会立即生成线程的时间片,因为旋转会阻止所有线程取得进展。For example, on single-core computers, SpinWait yields the time slice of the thread immediately because spinning blocks forward progress on all threads. 即使在多核计算机上,SpinWait 也会生成时间片,以防等待线程阻止优先级较高的线程或垃圾回收器。SpinWait also yields even on multi-core machines to prevent the waiting thread from blocking higher-priority threads or the garbage collector. 因此,若要在两阶段等待操作中使用 SpinWait,建议在 SpinWait 本身启动上下文切换前,先调用内核等待。Therefore, if you are using a SpinWait in a two-phase wait operation, we recommend that you invoke the kernel wait before the SpinWait itself initiates a context switch. SpinWait 提供每次调用 SpinOnce 前都可以检查的 NextSpinWillYield 属性。SpinWait provides the NextSpinWillYield property, which you can check before every call to SpinOnce. 如果此属性返回 true,启动自己的等待操作。When the property returns true, initiate your own Wait operation. 有关示例,请参阅如何:使用 SpinWait 实现两阶段等待操作For an example, see How to: Use SpinWait to Implement a Two-Phase Wait Operation.

如果不想执行两阶段等待操作,只是想一直旋转到某条件为 true,可以启用 SpinWait 执行上下文切换,让它成为 Windows 操作系统环境中的合法成员。If you are not performing a two-phase wait operation but are just spinning until some condition is true, you can enable SpinWait to perform its context switches so that it is a good citizen in the Windows operating system environment. 下面的基本示例展示了无锁堆栈中的 SpinWaitThe following basic example shows a SpinWait in a lock-free stack. 如果需要高性能的线程安全堆栈,请考虑使用 System.Collections.Concurrent.ConcurrentStack<T>If you require a high-performance, thread-safe stack, consider using System.Collections.Concurrent.ConcurrentStack<T>.

public class LockFreeStack<T>
{
    private volatile Node m_head;

    private class Node { public Node Next; public T Value; }

    public void Push(T item)
    {
        var spin = new SpinWait();
        Node node = new Node { Value = item }, head;
        while (true)
        {
            head = m_head;
            node.Next = head;
            if (Interlocked.CompareExchange(ref m_head, node, head) == head) break;
            spin.SpinOnce();
        }
    }

    public bool TryPop(out T result)
    {
        result = default(T);
        var spin = new SpinWait();

        Node head;
        while (true)
        {
            head = m_head;
            if (head == null) return false;
            if (Interlocked.CompareExchange(ref m_head, head.Next, head) == head)
            {
                result = head.Value;
                return true;
            }
            spin.SpinOnce();
        }
    }
}
Imports System.Threading

Module SpinWaitDemo


    Public Class LockFreeStack(Of T)
        Private m_head As Node

        Private Class Node
            Public [Next] As Node
            Public Value As T
        End Class

        Public Sub Push(ByVal item As T)
            Dim spin As New SpinWait()
            Dim head As Node, node As New Node With {.Value = item}

            While True
                Thread.MemoryBarrier()
                head = m_head
                node.Next = head
                If Interlocked.CompareExchange(m_head, node, head) Is head Then Exit While
                spin.SpinOnce()
            End While
        End Sub

        Public Function TryPop(ByRef result As T) As Boolean
            result = CType(Nothing, T)
            Dim spin As New SpinWait()

            Dim head As Node
            While True
                Thread.MemoryBarrier()
                head = m_head
                If head Is Nothing Then Return False
                If Interlocked.CompareExchange(m_head, head.Next, head) Is head Then
                    result = head.Value
                    Return True
                End If
                spin.SpinOnce()
            End While
        End Function
    End Class


End Module

另请参阅See also