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();
}
}
}