C# SpinWait详解

一、SpinWait的实现原理

 

SpinWait 类型的实现原理是使用自旋锁。自旋锁是一种无锁同步原语,它允许线程在获取锁之前自旋(即不断循环检查锁的状态)。

SpinWait 类型提供了以下方法来实现自旋等待:

  • SpinOnce():让当前线程自旋一次。
  • NextSpinWillYield:指示下一次自旋是否会让出 CPU 时间片。
  • SpinUntil(Func<bool>):自旋直到指定的条件为真。

SpinWait 类型的实现使用了一个名为 _count 的私有字段来跟踪自旋次数。当调用 SpinOnce() 方法时,_count 字段会增加 1。当调用 NextSpinWillYield 方法时,_count 字段会与一个阈值进行比较。如果 _count 字段大于或等于阈值,则方法返回 true,表示下一次自旋将让出 CPU 时间片。否则,方法返回 false,表示下一次自旋不会让出 CPU 时间片。

SpinUntil(Func<bool>) 方法使用一个循环来自旋,直到指定的条件为真。在每次循环中,方法都会调用 SpinOnce() 方法和 NextSpinWillYield 方法。如果 NextSpinWillYield 方法返回 true,则方法会调用 Thread.Yield() 方法来让出 CPU 时间片。否则,方法会继续自旋。

SpinWait 类型的实现还包括一个自适应自旋策略。该策略会根据当前系统的负载情况动态调整自旋次数。如果系统负载较高,则自旋次数会减少。如果系统负载较低,则自旋次数会增加。

自旋等待对于需要快速响应的低延迟并发场景非常有用,例如忙循环、锁自旋和无锁数据结构。它比线程阻塞更轻量级,并且不会导致上下文切换。

请注意,自旋等待是一个 CPU 密集型的操作,如果过度使用,可能会导致 CPU 使用率过高。因此,应谨慎使用 SpinWait 类型,并且仅在绝对必要时才使用。

二、实现源码

public struct SpinWait
{
    private int _count;

    public void SpinOnce()
    {
        if (_count > 0)
        {
            Thread.SpinWait(s_yieldThreshold);
        }
    }

    public void SpinUntil(Func<bool> condition)
    {
        while (_count > 0 && !condition())
        {
            SpinOnce();
        }
    }

    public void Reset()
    {
        _count = 0;
    }

    private static int s_yieldThreshold = Environment.ProcessorCount * 10;
}

三、Thread.SpinWai的实现

.

[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static void SpinWait(int iterations)
{
    if (iterations < 0)
    {
        throw new ArgumentOutOfRangeException(nameof(iterations), "iterations must be non-negative");
    }

    while (iterations > 0)
    {
        Thread.Yield();
        iterations--;
    }
}

Thread.Yield() 方法的 C# 实现如下:

[MethodImpl(MethodImplOptions.InternalCall)]
public static extern void Yield();

其中:

  • [MethodImpl(MethodImplOptions.InternalCall)] 特性表示该方法由公共语言运行时 (CLR) 本机代码实现。

Thread.Yield() 方法的本机实现因操作系统而异。在 Windows 上,Thread.Yield() 方法通过调用 SwitchToThread() 函数来实现,该函数将当前线程的状态设置为就绪状态,并将其移到就绪队列的末尾。

在 Linux 上,Thread.Yield() 方法通过调用 sched_yield() 函数来实现,该函数将当前线程的状态设置为可运行状态,并将其移到可运行队列的末尾。

在 macOS 上,Thread.Yield() 方法通过调用 pthread_yield() 函数来实现,该函数将当前线程的状态设置为可运行状态,并将其移到可运行队列的末尾。

Thread.Yield() 方法让出当前线程的 CPU 时间片,允许其他线程运行。这与 Thread.Sleep(0) 类似,但 Thread.Yield() 不保证线程立即让出 CPU 时间片,而 Thread.Sleep(0) 则保证线程立即让出 CPU 时间片。

Thread.Yield() 方法对于需要快速响应的低延迟场景非常有用,例如忙循环、锁自旋和无锁数据结构。它比 Thread.Sleep(0) 更轻量级,并且不会导致线程阻塞或上下文切换。

请注意,Thread.Yield() 方法是一个 CPU 密集型的操作,如果过度使用,可能会导致 CPU 使用率过高。因此,应谨慎使用 Thread.Yield() 方法,并且仅在绝对必要时才使用。

 

四、例子

 

private void SpinUntilCompleted() {
    if (Task.IsCompleted) {
        return;
    }

    var sw = new SpinWait();
    while (!Task.IsCompleted) {
        sw.SpinOnce();
    }
}
internal bool TrySpinWait()
{
    var wait = new SpinWait();
    do
    {
        wait.SpinOnce();
        if(Pending.HasResult(_key)) return true;
    } while (!wait.NextSpinWillYield);
    return false;
}
private void PushCore(Node head, Node tail)
{
    SpinWait spinWait = default;
    do
    {
        spinWait.SpinOnce();
        tail.m_next = m_head;
    }
    while (Interlocked.CompareExchange(ref m_head, head, tail.m_next) != tail.m_next);
}
private void PublicationOnlyWaitForOtherThreadToPublish()
{
    var spinWait = new SpinWait();
    while (!ReferenceEquals(_state, null))
    {
        // We get here when PublicationOnly temporarily sets _state to LazyHelper.PublicationOnlyWaitForOtherThreadToPublish.
        // This temporary state should be quickly followed by _state being set to null.
        spinWait.SpinOnce();
    }
}
public void Idle(int workCount)
{
    if (workCount > 0)
    {
        return;
    }

    _spinWait.SpinOnce();
}
public void Wait()
{
    SpinWait wait = new SpinWait();

    wait.SpinOnce();

    while (Count > 0)
        Thread.Sleep(1);
}
public T BlockingReadInbound<T>()
{
    T msg;
    var sw = new SpinWait();
    while ((msg = ReadInbound<T>()) == null)
    {
        sw.SpinOnce();
    }
    return msg;

}
public static void SpinWaitWhileNull<T>(ref T? check)
    where T : class
{
    var spinWait = new SpinWait();
    while (Volatile.Read(ref check) == null)
    {
        spinWait.SpinOnce();
    }
}
public void Enqueue(T item)
{
    SpinWait spin = new SpinWait();
    while (true)
    {
        Segment tail = _tail;
        if (tail.TryAppend(item))
            return;
        spin.SpinOnce();
    }
}
public void Add(ThreadPoolWorkItem item)
{
    TurboContract.Requires(item != null, conditionString: "item != null");

    SpinWait sw = new SpinWait();
    Segment tail = _tail;

    while (!tail.TryAdd(item))
    {
        sw.SpinOnce();
        tail = _tail;
    }
}
[DebuggerStepThrough]
public static void Update(ref int location, Func<int, int> generator)
{
    var spinner = new SpinWait();
    while (true)
    {
        var snapshot1 = location;
        var value = generator(snapshot1);
        var snapshot2 = Interlocked.CompareExchange(ref location, value, snapshot1);
        if (snapshot1.Equals(snapshot2)) { return; }
        spinner.SpinOnce();
    }
}
[MethodImpl(MethodImplOptions.NoInlining)]
private long SpinWaitForImpl(long expectedValue, CancellationToken cancellationToken)
{
    var spinWait = new SpinWait();
    long availableSequence;
    do
    {
        cancellationToken.ThrowIfCancellationRequested();
        spinWait.SpinOnce();
        availableSequence = Value;
    }
    while (availableSequence < expectedValue);

    return availableSequence;
}

 

 
 
posted @ 2024-05-10 10:28  卖雨伞的小男孩  阅读(213)  评论(0编辑  收藏  举报