SpinWait
System.Threading.SpinWait は軽量な同期型で、負荷が高いコンテキスト スイッチやカーネル イベントに必要なカーネル遷移を避けるために下位レベルのシナリオで使用できます。 マルチコア コンピューターでは、リソースの保持時間が長くならないと予測される場合、待機中のスレッドを数十または数百サイクルの間ユーザー モードでスピンさせてから、リソースの取得を再試行した方が効率的です。 スピン後にリソースを使用できる場合は、数千サイクルを節約したことになります。 リソースをまだ使用できない場合でも、数サイクルを消費しただけであり、カーネル ベースの待機に移行できます。 スピン後に待機というこの組み合わせは、2 フェーズ待機操作と呼ばれることがあります。
SpinWait は、ManualResetEvent などのカーネル イベントをラップする .NET Framework 型と共に使用するように設計されています。 また、SpinWait は、1 つのプログラムで基本的なスピン機能のために単独で使用することもできます。
SpinWait は単なる空のループではありません。 一般的に、適切なスピン動作を提供するためには慎重に実装する必要があり、長時間 (おおよそ、カーネル遷移に要する時間) スピンすると、コンテキスト スイッチが開始されます。 たとえば、シングルコア コンピューターでは、スピンはすべてのスレッドの進行をブロックするので、SpinWait はスレッドのタイム スライスを直ちに譲渡します。 マルチコア コンピューターでも、SpinWait は、待機中のスレッドがより優先順位の高いスレッドやガベージ コレクターをブロックしないように譲渡します。 したがって、SpinWait を 2 フェーズ待機操作で使用する場合は、SpinWait 自体がコンテキスト スイッチを開始する前にカーネル待機を呼び出すことをお勧めします。 SpinWait には NextSpinWillYield プロパティがあり、SpinOnce に対する毎回の呼び出しの前にこのプロパティをチェックできます。 このプロパティから true が返された場合、独自の待機操作が開始されます。 例については、「方法: SpinWait を使用して 2 フェーズ待機操作を実装する」を参照してください。
2 フェーズ待機操作は実行せずに、特定の条件が満たされるまでスピンを行うだけの場合は、Windows オペレーティング システム環境で問題を発生させることなく、SpinWait を有効にしてコンテキスト スイッチを実行できます。 ロック制御の不要なスタックの SpinWait を次の基本的な例に示します。 高パフォーマンスのスレッド セーフ スタックが必要な場合は、System.Collections.Concurrent.ConcurrentStack<T> の使用を検討してください。
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
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();
}
}
}