SpinWait
System.Threading.SpinWait 是一种轻型同步类型,可用于低级方案,以避免执行内核事件所需的高成本上下文切换和内核转换。 在多核计算机上,如果不得长时间保留资源,更高效的做法是,先让等待线程在用户模式下旋转几十或几百个周期,再重试获取资源。 如果资源在旋转后可用,便节省了几千个周期。 如果资源仍不可用,那么也只花了几个周期,仍可以进入基于内核的等待。 这种“旋转后等待”的组合有时称为“两阶段等待操作” 。
SpinWait 旨在与包装内核事件(如 ManualResetEvent)的 .NET 类型结合使用。 SpinWait 本身也可以仅在一个程序中用于提供基本的旋转功能。
SpinWait 不仅仅只是空循环。 谨慎实现后,它可以提供适用于一般情况的正确旋转行为,并且本身能够在旋转时间够长(大致是内核转换所需的时间长度)时自行启动上下文切换。 例如,在单核计算机上,SpinWait 会立即生成线程的时间片,因为旋转会阻止所有线程取得进展。 即使在多核计算机上,SpinWait 也会生成时间片,以防等待线程阻止优先级较高的线程或垃圾回收器。 因此,若要在两阶段等待操作中使用 SpinWait,建议在 SpinWait 本身启动上下文切换前,先调用内核等待。 SpinWait 提供每次调用 SpinOnce 前都可以检查的 NextSpinWillYield 属性。 如果此属性返回 true
,启动自己的等待操作。 有关示例,请参阅如何:使用 SpinWait 实现两阶段等待操作。
如果不想执行两阶段等待操作,只是想一直旋转到某条件为 true,可以启用 SpinWait 执行上下文切换,让它成为 Windows 操作系统环境中的合法成员。 下面的基本示例展示了无锁堆栈中的 SpinWait。 如果需要高性能的线程安全堆栈,请考虑使用 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