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


SpinWait

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

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

SpinWait это больше, чем просто пустой цикл. Он тщательно реализуется, чтобы обеспечить правильное поведение спиннинга для общего случая, и сам инициирует переключения контекста, если он работает достаточно долго (примерно продолжительность времени, необходимого для перехода ядра). Например, на одноядерных компьютерах SpinWait возвращает срез времени потока немедленно, так как спиннинг блокирует ход выполнения всех потоков. SpinWait также уступает даже на многопроцессорных системах, чтобы предотвратить блокировку ожидающих потоков с более высоким приоритетом или сборщика мусора. Поэтому, если вы используете SpinWait в двухэтапной операции ожидания, мы рекомендуем вызвать ожидание ядра, прежде чем SpinWait сам инициирует переключение контекста. SpinWait предоставляет свойство NextSpinWillYield, которое можно проверить перед каждым вызовом SpinOnce. Когда свойство возвращается true, инициируйте собственную операцию ожидания. Пример см. в статье "Практическое руководство. Использование SpinWait для реализации операции ожидания Two-Phase".

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

См. также