• C# SpinWait 实现


    其实SpinWait的code 非常简单,以前看过很多遍,但是从来都没有整理过,整理也是再次学习吧。

    我们先看看SpinWait的一些评论或者注意点吧:如果等待某个条件满足需要的时间很短,而且不希望发生昂贵的上下文切换,那么基于自旋的等待是一种很好的替换方案,SpinWait不仅提供了基本自旋功能,而且还提供了SpinWait.SpinUntil方法,使用这个方法能够自旋直到满足某个条件为止,此外SpinWait是一个Struct,从内存的角度上说,开销很小。SpinLock是对SpinWait的简单封装。需要注意的是:长时间的自旋不是很好的做法,因为自旋会阻塞更高级的线程及其相关的任务,还会阻塞垃圾回收机制。SpinWait并没有设计为让多个任务或线程并发使用,因此多个任务或线程通过SpinWait方法进行自旋,那么每一个任务或线程都应该使用自己的SpinWait实例。当一个线程自旋时,会将一个内核放入到一个繁忙的循环中,而不会让出当前处理器时间片剩余部分,当一个任务或者线程调用Thread.Sleep方法时,底层线程会让出当前处理器时间片的剩余部分,这是一个大开销的操作。因此,在大部分情况下, 不要在循环内调用Thread.Sleep方法等待特定的条件满足 

    其实现如下:

       [HostProtection(Synchronization = true, ExternalThreading = true)]
        public struct SpinWait
        {
            internal const int YIELD_THRESHOLD = 10; // When to switch over to a true yield.
            internal const int SLEEP_0_EVERY_HOW_MANY_TIMES = 5; // After how many yields should we Sleep(0)?
            internal const int SLEEP_1_EVERY_HOW_MANY_TIMES = 20; // After how many yields should we Sleep(1)?
    
            // The number of times we've spun already.
            private int m_count;
            public bool NextSpinWillYield
            {
                get { return m_count > YIELD_THRESHOLD || PlatformHelper.IsSingleProcessor; }
            }
            public void SpinOnce()
            {
                if (NextSpinWillYield)
                {              
                    int yieldsSoFar = (m_count >= YIELD_THRESHOLD ? m_count - YIELD_THRESHOLD : m_count);
                    if ((yieldsSoFar % SLEEP_1_EVERY_HOW_MANY_TIMES) == (SLEEP_1_EVERY_HOW_MANY_TIMES - 1))
                    {
                        Thread.Sleep(1);
                    }
                    else if ((yieldsSoFar % SLEEP_0_EVERY_HOW_MANY_TIMES) == (SLEEP_0_EVERY_HOW_MANY_TIMES - 1))
                    {
                        Thread.Sleep(0);
                    }
                    else
                    {
                        Thread.Yield();
                    }
                }
                else
                {
                    Thread.SpinWait(4 << m_count);
                }
                // Finally, increment our spin counter.
                m_count = (m_count == int.MaxValue ? YIELD_THRESHOLD : m_count + 1);
            }
            
            public static bool SpinUntil(Func<bool> condition, int millisecondsTimeout)
            {
                if (millisecondsTimeout < Timeout.Infinite)
                {
                    throw new ArgumentOutOfRangeException("millisecondsTimeout", millisecondsTimeout, Environment.GetResourceString("SpinWait_SpinUntil_TimeoutWrong"));
                }
                if (condition == null)
                {
                    throw new ArgumentNullException("condition", Environment.GetResourceString("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;
            }
            public void Reset()
            {
                m_count = 0;
            }
        }

    SpinWait的核心方是SpinOnce方法,假如我们是多核CPU,该方法在前10次【NextSpinWillYield为false】SpinOnce调用Thread.SpinWait【这个是一个win32的方法 , private static extern void SpinWaitInternal(int iterations);】,后面的调用主要是调用 Thread.Yield()【Win32API, private static extern bool YieldInternal()】,当count是14的时候第一次调用  Thread.Sleep(0),当count是29的时候第一次调用 Thread.Sleep(1),后的的规则是(count-10)%20==19 调用Thread.Sleep(1),否者检查(count-10)%5==4Thread.Sleep(0).

    SpinUntil方法的实现句非常简单了,就是循环调用SpinOnce方法,如果条件满足 或者超时 则退出循环。

    Thread.Yeild
    该方法是在 .Net 4.0 中推出的新方法,它对应的底层方法是 SwitchToThread。Yield 的中文翻译为 “放弃”,这里意思是主动放弃当前线程的时间片,并让操作系统调度其它就绪态的线程使用一个时间片。但是如果调用 Yield,只是把当前线程放入到就绪队列中,而不是阻塞队列。如果没有找到其它就绪态的线程,则当前线程继续运行。
    优势:比 Thread.Sleep(0) 速度要快,可以让低于当前优先级的线程得以运行。可以通过返回值判断是否成功调度了其它线程。
    劣势:只能调度同一个处理器的线程,不能调度其它处理器的线程。当没有其它就绪的线程,会一直占用 CPU 时间片,造成 CPU 100%占用率

    Thread.Sleep(0)
    Sleep 的意思是告诉操作系统自己要休息 n 毫秒,这段时间就让给另一个就绪的线程吧。当 n=0 的时候,意思是要放弃自己剩下的时间片,但是仍然是就绪状态,其实意思和 Yield 有点类似。但是 Sleep(0) 只允许那些优先级相等或更高的线程使用当前的CPU,其它线程只能等着挨饿了。如果没有合适的线程,那当前线程会重新使用 CPU 时间片。
    优势:相比 Yield,可以调度任何处理器的线程使用时间片。
    劣势:只能调度优先级相等或更高的线程,意味着优先级低的线程很难获得时间片,很可能永远都调用不到。当没有符合条件的线程,会一直占用 CPU 时间片,造成 CPU 100%占用率。

    Thread.Sleep(1)
    该方法使用 1 作为参数,这会强制当前线程放弃剩下的时间片,并休息 1 毫秒(因为不是实时操作系统,时间无法保证精确,一般可能会滞后几毫秒或一个时间片)。但因此的好处是,所有其它就绪状态的线程都有机会竞争时间片,而不用在乎优先级。
    优势:可以调度任何处理器的线程使用时间片。无论有没有符合的线程,都会放弃 CPU 时间,因此 CPU 占用率较低。
    劣势:相比 Thread.Sleep(0),因为至少会休息一定时间,所以速度要更慢。

    Thread.Sleep(0) vs Sleep(1) vs Yeild

  • 相关阅读:
    能够分页显示的Label控件
    C# winform 捕获全局异常
    纯C#钩子实现及应用
    C#对App.config文件或者web.config文件中节点的操作类
    C#中强制关闭某个进程
    VS2005中服务的启动,安装与卸载
    获取数据库表结构和表数据的小程序(VB.NET版本)
    使用ImessageFilter接口实现截获键盘或者鼠标的消息
    Windows_API_函数 参考大全
    系统升级程序的介绍
  • 原文地址:https://www.cnblogs.com/majiang/p/7889584.html
Copyright © 2020-2023  润新知