Partager via


SpinWait

System.Threading.SpinWait est un type de synchronisation léger que vous pouvez utiliser dans des scénarios de bas niveau pour éviter les changements de contexte et les transitions de noyau complexes requis pour les événements de noyau. Sur les ordinateurs multicœurs, lorsqu'une ressource ne doit pas être conservée pendant longtemps, il peut être plus efficace pour un thread en attente de tourner en mode utilisateur pendant quelques douzaines ou centaines de cycles, puis de réessayer d'acquérir la ressource. Si la ressource est disponible après rotation, vous avez économisé plusieurs milliers de cycles. Si la ressource n'est toujours pas disponible, vous n'avez utilisé que quelques cycles et pouvez encore entrer une attente basée sur le noyau. Cette combinaison rotation, puis attente est parfois appelée opération d'attente à deux phases.

SpinWait est conçu pour être utilisé avec les types .NET Framework qui incluent dans un wrapper des événements de noyau tels que ManualResetEvent. SpinWait peut également être utilisé par lui-même pour des fonctionnalités de rotation de base dans un seul programme.

SpinWait est plus qu'une simple boucle vide. Il est implémenté avec soin pour fournir un comportement de rotation correct pour les cas généraux et initialise lui-même des changements de contexte s'il tourne assez longtemps (environ la durée requise pour une transition de noyau). Par exemple, sur les ordinateurs à cœur unique, SpinWait génère immédiatement la tranche horaire du thread car la rotation bloque la progression sur tous les threads. SpinWait génère même sur les ordinateurs multicœurs pour empêcher le thread en attente de bloquer des threads de priorité plus élevée ou le garbage collector. Par conséquent, si vous utilisez un SpinWait dans une opération d'attente à deux phases, nous vous recommandons d'appeler l'attente de noyau avant que le SpinWait lui-même n'initialise un changement de contexte. SpinWait fournit la propriété NextSpinWillYield, que vous pouvez vérifier avant chaque appel à SpinOnce. Lorsque la propriété retourne la valeur true, initialisez votre propre opération d'attente. Pour obtenir un exemple, consultez Comment : utiliser SpinWait pour implémenter une opération d'attente en deux phases.

Si vous n'exécutez pas d'opération d'attente à deux phases mais que vous tournez simplement en attendant qu'une condition soit remplie, vous pouvez permettre à SpinWait d'effectuer ses changements de contexte afin d'être un bon citoyen de l'environnement de système d'exploitation Windows. L'exemple de base suivant montre un SpinWait dans une pile sans verrou. Si vous avez besoin d'une pile thread-safe haute performance, envisagez d'utiliser 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();
        }
    }
}

Voir aussi

Référence

SpinWait

Autres ressources

Fonctionnalités et objets de threading