SpinWait

System.Threading.SpinWait est un type de synchronisation léger que vous pouvez utiliser dans des scénarios de bas niveau pour éviter des changements de contexte onéreux et des transitions de noyau requises pour les événements de noyau. Sur les ordinateurs multicœurs, lorsqu’une ressource n’est pas censée être maintenue pendant de longues périodes, 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 la rotation, vous aurez économisé plusieurs milliers de cycles. Si la ressource n’est toujours pas disponible, vous n’aurez utilisé que quelques cycles et pourrez toujours entrer une attente basée sur le noyau. Cette combinaison de rotation et d’attente est parfois appelée une opération d’attente en deux phases.

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

SpinWait est plus qu’une simple boucle vide. Il est implémenté avec soin afin de fournir le comportement de rotation correct pour le cas général et initialise lui-même des changements de contexte s’il tourne assez longtemps (à peu près le temps nécessaire pour une transition de noyau). Par exemple, sur les ordinateurs à cœur unique, SpinWait interrompt immédiatement la tranche horaire du thread , car la rotation bloque la progression sur tous les threads. SpinWait cesse même temporairement l’exécution sur les ordinateurs multicœurs pour empêcher le thread en attente de bloquer des threads à priorité plus élevée ou le récupérateur de mémoire. Par conséquent, si vous utilisez un SpinWait dans une opération d’attente en deux phases, nous vous recommandons d’appeler l’attente de noyau avant que le SpinWait lui-même initie un changement de contexte. SpinWait fournit la propriété NextSpinWillYield, que vous pouvez vérifier avant chaque appel à SpinOnce. Lorsque la propriété retourne true, initialisez votre propre opération d’attente. Pour un exemple, consultez le Guide pratique d’utilisation de SpinWait pour implémenter une opération d’attente en deux phases.

Si vous n’effectuez pas une opération d’attente en deux phases, mais uniquement des rotations jusqu'à ce qu’une condition soit remplie, vous pouvez activer SpinWait pour effectuer des changements de contexte comme il convient dans 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 haute performance sécurisée au niveau des threads, envisagez d’utiliser 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

Voir aussi