SpinWait

System.Threading.SpinWait は軽量な同期型であり、負荷が高いコンテキスト スイッチとカーネル イベントに必要なカーネル遷移を避けるために低レベルのシナリオで使用できます。 マルチコア コンピューターでは、リソースの保持期間が長くならないと予測される場合、待機中のスレッドを数十または数百サイクルの間ユーザー モードでスピンさせてから、リソースの取得を再試行した方が効率的です。 スピン後にリソースを使用できる場合は、数千サイクルを節約したことになります。 リソースをまだ使用できない場合でも、数サイクルを消費しただけであり、カーネル ベースの待機に移行できます。 スピン後に待機というこの組み合わせは、2 フェーズ待機操作 と呼ばれることがあります。

SpinWait は、ManualResetEvent などのカーネル イベントをラップする .NET 型と共に使用するように設計されています。 また、SpinWait は、1 つのプログラムのみで基本的なスピン機能のために単独で使用することもできます。

SpinWait は単なる空のループではありません。 一般的に、適切なスピン動作を提供するためには慎重に実装する必要があり、長時間 (おおよそ、カーネル遷移に要する時間) スピンすると、コンテキスト スイッチが開始されます。 たとえば、シングルコア コンピューターでは、スピンはすべてのスレッドの進行をブロックするため、SpinWait によって、スレッドのタイム スライスがすぐに生成されます。 マルチコア コンピューターでも、待機中のスレッドがより優先順位の高いスレッドやガベージ コレクターをブロックしないように、SpinWait によってタイム スライスが生成されます。 したがって、SpinWait を 2 フェーズ待機操作で使用する場合は、SpinWait 自体がコンテキスト スイッチを開始する前にカーネル待機を呼び出すことをお勧めします。 SpinWait では NextSpinWillYield プロパティが提供され、SpinOnce のすべての呼び出しの前に確認できます。 プロパティが true を返したときに、独自の待機操作を開始します。 例については、「方法: SpinWait を使用して 2 フェーズ待機操作を実装する」を参照してください。

2 フェーズ待機操作は実行せずに、特定の条件が満たされるまでスピンを行うだけの場合は、Windows オペレーティング システム環境で問題を発生させることなく、SpinWait を有効にしてコンテキスト スイッチを実行できます。 ロックされないスタックの 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

参照