Condividi tramite


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

Vedere anche

Riferimenti

SpinWait

Altre risorse

Oggetti e funzionalità del threading