欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页  >  IT编程

C#番外篇-SpinWait

程序员文章站 2022-04-02 20:20:14
SpinWait封装常见旋转逻辑。在单处理器计算机上,始终使用 “生成” 而不是 “繁忙等待”,在装有超线程技术的 Intel 处理器的计算机上,这有助于防止硬件线程不足。SpinWait 封装了一种很好的旋转和真正的生成。SpinWait是一个值类型,这意味着低级别代码可以使用 SpinWait,而不必担心不必要的分配开销。SpinWait 对于普通应用程序通常不起作用。在大多数情况下,应使用由 .NET Framework 提供的同步类,如 Monitor 。但在需要自旋等待的大多数情况下, Spin...

SpinWait封装常见旋转逻辑。在单处理器计算机上,始终使用 “生成” 而不是 “繁忙等待”,在装有超线程技术的 Intel 处理器的计算机上,这有助于防止硬件线程不足。SpinWait 封装了一种很好的旋转和真正的生成。

SpinWait是一个值类型,这意味着低级别代码可以使用 SpinWait,而不必担心不必要的分配开销。SpinWait 对于普通应用程序通常不起作用。在大多数情况下,应使用由 .NET Framework 提供的同步类,如 Monitor 。但在需要自旋等待的大多数情况下, SpinWait 类型应优先于 Thread.SpinWait 方法。

System.Threading.SpinWait 是一种轻型同步类型,可用于低级方案,以避免执行内核事件所需的高成本上下文切换和内核转换。在多核计算机上,如果不得长时间保留资源,更高效的做法是,先让等待线程在用户模式下旋转几十或几百个周期,再重试获取资源。如果资源在旋转后可用,便节省了几千个周期。如果资源仍不可用,那么也只花了几个周期,仍可以进入基于内核的等待。这种“旋转后等待”的组合有时称为“两阶段等待操作” 。

SpinWait 旨在与包装内核事件(如 ManualResetEvent)的 .NET Framework 类型结合使用。SpinWait 本身也可以仅在一个程序中用于提供基本的旋转功能。

SpinWait 不仅仅只是空循环。谨慎实现后,它可以提供适用于一般情况的正确旋转行为,并且本身能够在旋转时间够长(大致是内核转换所需的时间长度)时自行启动上下文切换。例如,在单核计算机上,SpinWait 会立即生成线程的时间片,因为旋转会阻止所有线程取得进展。即使在多核计算机上,SpinWait 也会生成时间片,以防等待线程阻止优先级较高的线程或垃圾回收器。因此,若要在两阶段等待操作中使用 SpinWait,建议在 SpinWait 本身启动上下文切换前,先调用内核等待。SpinWait 提供每次调用 SpinOnce 前都可以检查的 NextSpinWillYield 属性。如果此属性返回 true,启动自己的等待操作。


看完官方说明一脸懵逼,将上面的语言用通俗的话来说,Thread.Sleep方法在执行时,会将阻止的时间的CPU切换至其他等待的进程,等到Thread.Sleep等待时间到后,再获取CPU的控制权继续执行下一步操作;SpinWait提供了While循环方法,在等待通过循环来阻止当前CPU的释放,一直等待当前方法执行完成然后释放。我们都知道进程在切换的时候会有时间与内存的消耗,所以尽可能使用SpinWait替代Thread.Sleep。

现在我们看下SpinWait结构中的代码:

/// <summary>
/// 循环一次
/// </summary>
/// <remarks>
/// This is typically called in a loop, and may change in behavior based on  the number of times a
/// <see cref="SpinOnce"/> has been called thus far on this instance.
/// </remarks>
public void SpinOnce()
{
    if (NextSpinWillYield)
    {
        int yieldsSoFar = (m_count >= YIELD_THRESHOLD ? m_count -  YIELD_THRESHOLD : m_count);
        //③循环到20次时,执行Thread.Sleep(01)
        if ((yieldsSoFar % SLEEP_1_EVERY_HOW_MANY_TIMES) ==  (SLEEP_1_EVERY_HOW_MANY_TIMES - 1))
        {
            //当前线程挂起,让出cpu
            //所有挂起的线程都有机会竞争当前时间片段,不限制线程优先级
            Thread.Sleep(1);
        }
        //②执行Thread.Yield()5次后,执行Thread.Sleep(0)   
        else if ((yieldsSoFar % SLEEP_0_EVERY_HOW_MANY_TIMES) ==  (SLEEP_0_EVERY_HOW_MANY_TIMES - 1))
        {
            //当前线程挂起,让出cpu
            //(只允许那些优先级相等或更高的线程使用当前的CPU。
            //如果没有,那当前线程会重新使用CPU时间片)
            //(上面已说明,后续补充实现)
            Thread.Sleep(0);
        }
        else
        {
            //当前线程挂起(执行状态->就绪状态), 让出cpu,
            //(后续补充实现逻辑)
            Thread.Yield();
        }
    }
    else
    {
        //线程等待
        //4,8,16,32,64...位运算,2的n次方
        //①循环10次
        Thread.SpinWait(4 << m_count);
    }
    // m_count 递增; m_count 达到最大值后回滚Count =10
    m_count = (m_count == int.MaxValue ? YIELD_THRESHOLD : m_count + 1);
}
/// <summary>
/// 重置循环计数器
/// </summary>
public void Reset()
{
    m_count = 0;
}
#region Static Methods
/// <summary>
/// 循环.直到condition返回True
/// </summary>
public static void SpinUntil(Func<bool> condition)
{
    SpinUntil(condition, Timeout.Infinite);
}
/// <summary>
/// 循环,直到condition返回True或者时间达到timeout
/// </summary>
public static bool SpinUntil(Func<bool> condition, TimeSpan timeout)
{
    //校验时间格式是否正确
    Int64 totalMilliseconds = (Int64)timeout.TotalMilliseconds;
    if (totalMilliseconds < -1 || totalMilliseconds > Int32.MaxValue)
    {
        throw new System.ArgumentOutOfRangeException(
            "timeout", timeout, "SpinWait_SpinUntil_TimeoutWrong");
    }
    return SpinUntil(condition, (int)timeout.TotalMilliseconds);
}
/// <summary>
/// 直到condition返回True或者时间达到timeout.
/// </summary>
public static bool SpinUntil(Func<bool> condition, int millisecondsTimeout)
{
    //校验时间格式
    if (millisecondsTimeout < Timeout.Infinite)
    {
        throw new ArgumentOutOfRangeException(
           "millisecondsTimeout", millisecondsTimeout,  "SpinWait_SpinUntil_TimeoutWrong");
    }
    //空值校验
    if (condition == null)
    {
        throw new ArgumentNullException("condition",  "SpinWait_SpinUntil_ArgumentNull");
    }
    uint startTime = 0;
    if (millisecondsTimeout != 0 && millisecondsTimeout != Timeout.Infinite)
    {
        //自上次启动计算机以来所经过的时间(以毫秒为单位)。
        startTime = TimeoutHelper.GetTime();
    }
    SpinWait spinner = new SpinWait();
    while (!condition())
    {
        if (millisecondsTimeout == 0)
        {
            return false;
        }
        spinner.SpinOnce();
        //计时
        if (millisecondsTimeout != Timeout.Infinite &&  spinner.NextSpinWillYield)
        {
            if (millisecondsTimeout <= (TimeoutHelper.GetTime() - startTime))
            {
                return false;
            }
        }
    }
    return true;
}
#endregion

C#番外篇-SpinWait

本文地址:https://blog.csdn.net/xuetian0546/article/details/109244724

相关标签: C#