Поделиться через


SpinWait

System.Threading.SpinWait представляет собой упрощенный тип синхронизации, который позволяет обойтись в низкоуровневых сценариях без ресурсоемких переключений контекста и изменений режима ядра, которые выполняются для событий ядра. На многоядерных компьютерах, если сценарий не подразумевает долгосрочную блокировку ресурсов, эффективнее всего применить цикл ожидания в пользовательском режиме для потока в состоянии ожидания, который после нескольких десятков или сотен повторений снова попытается обратиться к ресурсу. Если ресурс окажется доступен после выхода из такого цикла, вы сэкономите несколько тысяч тактов процессора. Если же ресурс будет недоступен, то вы потратили лишь несколько циклов и можете снова применить ожидание на уровне ядра. Такой подход с циклом и последующим ожиданием иногда называют двухэтапной операцией ожидания.

SpinWait предназначен для использования в сочетании с типами .NET, которые являются оболочкой событий ядра, например ManualResetEvent. SpinWait также можно использовать отдельно, чтобы реализовать простой процесс ожидания в одной программе.

Не следует считать SpinWait обычным пустым циклом. Он тщательно реализован с таким расчетом, чтобы правильно выполнять описанное поведение в разных ситуациях, и может самостоятельно переключать контекст, если ожидание оказывается слишком долгим. Долгим считается интервал времени, примерно соответствующий скорости перехода в режим ядра. Например, на одноядерных компьютерах SpinWait немедленно прерывает квант времени, выделенный потоку, так как цикл ожидания в этой ситуации заблокирует выполнение всех потоков. Также SpinWait немедленно приостанавливается даже на многоядерных компьютерах, если поток в состоянии ожидания может заблокировать потоки с более высоким приоритетом или сборщик мусора. Таким образом, если вы используете SpinWait для двухэтапной операции ожидания, мы рекомендуем вручную вызвать ожидание ядра до того, как SpinWait инициирует переключение контекста. SpinWait предоставляет свойство NextSpinWillYield, которое следует проверять перед каждым вызовом SpinOnce. Если это свойство возвращает true, используйте собственную операцию ожидания. Пример реализации см. в статье Практическое руководство. Использование объекта SpinWait для реализации двухэтапной операции ожидания.

Если вы просто выполняете цикл ожидания до наступления некоторого условия, не используя двухэтапную операцию ожидания, можно позволить SpinWait самому переключать контекст, чтобы он соблюдал все условия и принципы корректной работы в операционной системе Windows. В следующем простом примере SpinWait применяется в стеке без блокировки. Если вам нужен потокобезопасный стек с высокой производительностью, используйте 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

См. также