SpinWait
System.Threading.SpinWait è un tipo di sincronizzazione leggero che è possibile utilizzare in scenari di basso livello per evitare le transizioni del kernel e i cambi di contesto dispendiosi necessari per gli eventi del kernel. Nei computer multicore, quando una risorsa non è destinata a essere conservata per lunghi periodi di tempo, può risultare più efficiente per un thread in attesa eseguire uno spin in modalità utente per alcune decine o centinaia di cicli, quindi ritentare di acquisire la risorsa. Se la risorsa è disponibile dopo lo spin, saranno state risparmiate diverse migliaia di cicli. Se la risorsa continua a non essere disponibile, sono stati utilizzati solo pochi cicli ed è ancora possibile accedere a un'attesa basata sul kernel. Questa combinazione di spin e attesa viene talvolta definita operazione di attesa a due fasi.
SpinWait è pensato per essere utilizzato con i tipi di .NET Framework che eseguono il wrapping di eventi del kernel quale ad esempio ManualResetEvent. SpinWait può inoltre essere utilizzato da solo per la funzionalità di spin di base in un unico programma.
SpinWait è più di un semplice ciclo vuoto. Viene implementato con cautela per fornire un comportamento di spin corretto per i casi generici e inizializza esso stesso cambi di contesto se lo spin è sufficientemente lungo (all'incirca la durata richiesta per una transizione del kernel). Nei computer singlecore, ad esempio, SpinWait restituisce immediatamente la porzione di tempo del thread poiché lo spin blocca l'avanzamento in tutti i thread. SpinWait restituisce inoltre, anche nei computer multicore, per impedire che il thread in attesa blocchi i thread con priorità più alta o il Garbage Collector. Pertanto, se si utilizza SpinWait in un'operazione di attesa a due fasi, è consigliabile richiamare l'attesa del kernel prima che SpinWait stesso avvii un cambio di contesto. SpinWait fornisce la proprietà NextSpinWillYield, che è possibile controllare prima di ogni chiamata a SpinOnce. Se la proprietà restituisce true, avviare l'operazione Wait personalizzata. Per un esempio, vedere Procedura: utilizzare SpinWait per implementare un'operazione di attesa a due fasi.
Se non si esegue un'operazione di attesa a due fasi bensì solo uno spin finché non si verifica una condizione, è possibile abilitare SpinWait per eseguirne i cambi di contesto in modo che sia un elemento positivo nell'ambiente del sistema operativo Windows. Nell'esempio di base seguente viene illustrato un oggetto SpinWait in un stack privo di blocchi. Se occorre uno stack thread-safe ad elevate prestazioni, utilizzare 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();
}
}
}