Compartilhar via


SpinWait

System.Threading.SpinWait é um tipo de sincronização leve que você pode usar em cenários de baixo nível a fim de evitar as alternâncias dispendiosas de contexto e transições de kernel necessárias para eventos de kernel. Em computadores com vários núcleos, quando espera-se que um recurso não seja mantido por muito tempo, talvez seja mais eficiente para um thread em espera executar no modo de usuário durante algumas dezenas ou centenas de ciclos e, em seguida, tentar novamente para adquirir o recurso. Se o recurso estiver disponível após a rotação, você terá salvo vários milhares de ciclos. Se o recurso ainda não estiver disponível, você terá passado apenas alguns ciclos e ainda poderá inserir uma espera baseada em kernel. Essa combinação de rotação e espera é, às vezes, conhecida como uma operação bifásica de espera.

O SpinWait foi projetado para ser usado em conjunto com os tipos do .NET que encapsulam os eventos de kernel como ManualResetEvent. SpinWait também pode ser usado por si só para a funcionalidade básica de rotação em apenas um programa.

SpinWait é mais do que apenas um loop vazio. É implementado com cuidado para fornecer o comportamento de rotação correto para o caso geral, e iniciará por conta própria as alternâncias de contexto se ficar em rotação por um tempo suficiente (aproximadamente o período necessário para uma transição de kernel). Por exemplo, em computadores de núcleo único, SpinWait produz imediatamente a fatia de tempo do thread, pois os blocos giratório encaminham progresso em todos os threads. SpinWait também dá resultado mesmo em máquinas com vários núcleos, a fim de impedir que o thread de espera bloqueie threads de prioridade mais alta ou o coletor de lixo. Portanto, se você estiver usando um SpinWait em uma operação de espera de duas fases, recomendamos que você invoque a espera do kernel antes de o próprio SpinWait iniciar uma alternância de contexto. SpinWait fornece a propriedade NextSpinWillYield, a qual você pode verificar antes de cada chamada para SpinOnce. Quando a propriedade retornar true, inicie sua própria operação de espera. Por exemplo, confira Como usar SpinWait para implementar uma operação bifásica de espera.

Se você não estiver executando uma operação bifásica de espera, mas estiver apenas em rotação até que alguma condição seja verdadeira, permita que SpinWait execute suas alternâncias de contexto para que seja um bom cidadão no ambiente do sistema operacional Windows. O exemplo básico a seguir mostra um SpinWait em uma pilha sem bloqueio. Se você precisar de uma pilha de alto desempenho e thread-safe, considere o uso de 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

Confira também