Compartir vía


SpinWait

System.Threading.SpinWait es un tipo de sincronización ligero que puede usar en escenarios de bajo nivel para evitar los costosos cambios de contexto y las transiciones de kernel que se requieren para los eventos de kernel. En los equipos con varios núcleos, cuando no se espera que un recurso se mantenga durante largos períodos de tiempo, puede resultar más eficaz que un subproceso en espera itere en modo de usuario en unas docenas o centenas de ciclos y que luego vuelva a intentar adquirir el recurso. Si el recurso está disponible después de girar, entonces habrá guardado varios miles de ciclos. Si el recurso aún no está disponible, habrá empleado solo algunos ciclos y podrá activar aún una espera basada en el kernel. A esta combinación de giro y espera se le denomina a veces una operación de espera de dos fases.

SpinWait está diseñado para usarse junto con los tipos de .NET que encapsulan eventos de kernel como ManualResetEvent. SpinWait también puede utilizarse por sí solo para la funcionalidad de giro básica en un único programa.

SpinWait es algo más que un bucle vacío. Se implementa con cuidado para proporcionar un comportamiento de giro correcto para el caso general e iniciará por sí mismo cambios de contexto si gira durante el tiempo suficiente (aproximadamente el período de tiempo necesario para una transición del kernel). Por ejemplo, en equipos de un único núcleo, SpinWait da como resultado el intervalo de tiempo del subproceso inmediatamente porque el giro bloquea el progreso en todos los subprocesos. SpinWait también se produce incluso en equipos de varios núcleos para evitar que el subproceso en espera bloquee subprocesos de mayor prioridad o el recolector de elementos no utilizados. Por lo tanto, si usa SpinWait en una operación de espera de dos fases, recomendamos que invoque la espera del kernel antes de que SpinWait inicie un cambio de contexto. SpinWait proporciona la propiedad NextSpinWillYield, que puede comprobar antes de cada llamada a SpinOnce. Cuando se devuelve la propiedad true, inicie su propia operación de espera. Para consultar un ejemplo, vea Usar SpinWait para implementar una operación de espera de dos fases.

Si no se está realizando una operación de espera de dos fases, sino que solo gira hasta que una condición sea true, puede habilitar SpinWait para realizar sus cambios de contexto, para actuar correctamente en el entorno del sistema operativo Windows. En el siguiente ejemplo básico, se muestra SpinWait en una pila sin bloqueo. Si necesita una pila segura para subprocesos y de alto rendimiento, considere la posibilidad de usar 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

Vea también