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


SpinWait

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

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

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

Если вместо двухэтапной операции ожидания просто выполняется цикл до выполнения некоторого условия, можно разрешить объекту SpinWait самому выполнять переключение контекста, чтобы он был "вежливым" в среде операционной системы Windows. В следующем базовом примере показано применение SpinWait в стеке без блокировок. Если необходимо создать высокопроизводительный потокобезопасный стек, следует рассмотреть возможность использования System.Collections.Concurrent.ConcurrentStack<T>.

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
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();
        }
    }
}

См. также

Ссылки

SpinWait

Другие ресурсы

Объекты и функциональные возможности работы с потоками