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