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