SpinWait

System.Threading.SpinWait ist ein einfacher Synchronisierungstyp, den Sie in Szenarien auf niedriger Ebene verwenden können, um die aufwändigen Kontextwechsel und Kernelübergänge zu vermeiden, die für Kernelereignisse erforderlich sind. Wenn bei Computern mit mehreren Kernen nicht erwartet wird, dass eine Ressource für längere Zeit beibehalten wird, kann es effizienter sein, wenn ein wartender Thread für ein paar Dutzend oder Hundert Zyklen im Benutzermodus rotiert und dann versucht wird, die Ressource zu erlangen. Wenn die Ressource nach den Spinvorgängen verfügbar ist, haben Sie mehrere Tausend Zyklen gespeichert. Wenn die Ressource immer noch nicht verfügbar ist, haben Sie nur wenige Zyklen verbraucht und können weiterhin in einen kernelbasierten Wartevorgang eintreten. Diese Kombination aus Spin- und Wartevorgang wird manchmal als zweiphasiger Wartevorgang bezeichnet.

SpinWait soll in Verbindung mit .NET-Typen verwendet werden, die Kernelereignisse wie ManualResetEvent umschließen. SpinWait kann auch eigenständig für grundlegende Spinfunktionalität in nur einem Programm verwendet werden.

SpinWait ist mehr als nur eine leere Schleife. Es ist sorgfältig implementiert, um das richtige Spinverhalten für den Allgemeinfall bereitzustellen, und initiiert selbst Kontextwechsel, wenn es ausreichend lange rotiert hat (ungefähr der für einen Kernelübergang erforderliche Zeitraum). Auf Einzelkerncomputern erzeugt SpinWait z.B. sofort das Zeitsegment des Threads, da sich drehende Blöcke den Status an alle Threads weiterleiten. SpinWait erzeugt sie ebenfalls, sogar auf Computern mit mehreren Kernen, um zu verhindern, dass der wartende Thread Threads mit höherer Priorität oder den Garbage Collector blockiert. Aus diesem Grund sollten Sie bei Verwendung von SpinWait in einem zweiphasigen Wartevorgang die Kernelwartezeit aufrufen, bevor SpinWait selbst einen Kontextwechsel initiiert. SpinWait stellt die NextSpinWillYield-Eigenschaft bereit, die Sie vor jedem Aufruf von SpinOnce überprüfen können. Wenn die Eigenschaft true zurückgibt, initiieren Sie Ihren eigenen Wartevorgang. Ein Beispiel finden Sie unter Gewusst wie: Implementieren eines Wartevorgangs mit zwei Phasen mit SpinWait.

Wenn Sie keinen zweiphasigen Wartevorgang ausführen, sondern einfach Schleifendurchläufe, bis eine Bedingung wahr ist, können Sie SpinWait aktivieren, um dessen Kontextwechsel auszuführen, damit es ein „guter Bürger“ in der Windows-Betriebssystemumgebung ist. Das folgende grundlegende Beispiel zeigt ein SpinWait in einem sperrenfreien Stapel. Wenn Sie einen threadsicheren Stapel mit hoher Leistung benötigen, ziehen Sie System.Collections.Concurrent.ConcurrentStack<T> in Betracht.

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

Weitere Informationen