Condividi tramite


SpinWait

System.Threading.SpinWait è un tipo di sincronizzazione leggero che è possibile usare in scenari di basso livello per evitare i costosi cambi di contesto e le transizioni del kernel necessarie per gli eventi del kernel. Nei computer multicore, quando non si prevede che una risorsa venga mantenuta per lunghi periodi di tempo, può essere più efficiente per un thread in attesa di ruotare in modalità utente per poche decine o poche centinaia di cicli e quindi riprovare ad acquisire la risorsa. Se la risorsa è disponibile dopo la rotazione, sono state salvate diverse migliaia di cicli. Se la risorsa non è ancora disponibile, hai impiegato solo pochi cicli e puoi comunque entrare in un'attesa basata su kernel. Questa combinazione di rotazione in attesa viene talvolta definita operazione di attesa in due fasi.

SpinWait è progettato per essere usato in combinazione con i tipi .NET che avvolgono eventi del kernel, come ManualResetEvent. SpinWait può anche essere usato da solo per la funzionalità di rotazione di base in un solo programma.

SpinWait è più di un semplice ciclo vuoto. Viene implementato attentamente per fornire un comportamento di rotazione corretto per il caso generale e avvierà le opzioni di contesto se ruota abbastanza a lungo (approssimativamente il periodo di tempo necessario per una transizione del kernel). Ad esempio, nei computer a core singolo, SpinWait restituisce immediatamente la sezione temporale del thread perché la rotazione blocca l'avanzamento in avanti su tutti i thread. SpinWait cede anche su computer multi-core per impedire al thread in attesa di bloccare i thread con priorità più alta o il raccoglitore di memoria. Pertanto, se si utilizza un 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 NextSpinWillYield proprietà , che è possibile controllare prima di ogni chiamata a SpinOnce. Quando la proprietà restituisce true, avvia la tua operazione di attesa. Per un esempio, vedere Procedura: Usare SpinWait per implementare un'operazione di attesa Two-Phase.

Se non si esegue un'operazione di attesa in due fasi ma ci si limita ad attendere in loop fino a quando non viene soddisfatta una condizione, è possibile abilitare SpinWait per eseguire i cambi di contesto, così che funzioni correttamente nell'ambiente del sistema operativo Windows. Nell'esempio di base seguente viene illustrato un oggetto SpinWait in uno stack senza blocchi. Se è necessario uno stack thread-safe ad alte prestazioni, è consigliabile usare 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

Vedere anche