其实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

---------------------
作者:dz45693
来源:CSDN
原文:https://blog.csdn.net/ma_jiang/article/details/78623306
版权声明:本文为博主原创文章,转载请附上博文链接!

SpinWait的更多相关文章

  1. 【C#】【Thread】SpinWait

    System.Threading.SpinWait 是一个轻量同步类型,可以在低级别方案中使用它来避免内核事件所需的高开销的上下文切换和内核转换. 在多核计算机上,当预计资源不会保留很长一段时间时,如 ...

  2. 重新想象 Windows 8 Store Apps (48) - 多线程之其他辅助类: SpinWait, SpinLock, Volatile, SynchronizationContext, CoreDispatcher, ThreadLocal, ThreadStaticAttribute

    [源码下载] 重新想象 Windows 8 Store Apps (48) - 多线程之其他辅助类: SpinWait, SpinLock, Volatile, SynchronizationCont ...

  3. C#并行编程 (Barrier,CountdownEvent,ManualResetEventSlim,SemaphoreSlim,SpinLock,SpinWait )

    背景 有时候必须访问变量.实例.方法.属性或者结构体,而这些并没有准备好用于并发访问,或者有时候需要执行部分代码,而这些代码必须单独运行,这是不得不通过将任务分解的方式让它们独立运行. 当任务和线程要 ...

  4. 线程同步-SpinWait

    这次将描述如何不适用内核模式的方式来使线程等待.SpinWait,它是一个混合同步构造,被设计为使用用户模式等待一段时间,然后切换到内核模式以节省CPU时间. 代码Demo: using System ...

  5. C# SpinWait 实现

    其实SpinWait的code 非常简单,以前看过很多遍,但是从来都没有整理过,整理也是再次学习吧. 我们先看看SpinWait的一些评论或者注意点吧:如果等待某个条件满足需要的时间很短,而且不希望发 ...

  6. C#SpinWait和volatile一点温习

    今天看ConcurrentQueue<T> 源码发现里面居然没有用到lock,我记得ConcurrentDictionary里面是有lock的,lock的是字典里面每一个key,但是Con ...

  7. C# SpinWait

    其实SpinWait的code 非常简单,以前看过很多遍,但是从来都没有整理过,整理也是再次学习吧. 我们先看看SpinWait的一些评论或者注意点吧:如果等待某个条件满足需要的时间很短,而且不希望发 ...

  8. [.net 多线程]SpinWait

    <CLR via C#>读书笔记-线程同步(四) 混合线程同步构造简介 之前有用户模式构造和内核模式构造,前者快速,但耗费CPU:后者可以阻塞线程,但耗时.耗资源.因此.NET会有一些混合 ...

  9. 《C#多线程编程实战》2.10 SpinWait

    emmm 这个SpinWait 中文是自旋等待的意思. 所谓自旋,就是自己追自己影子,周伯通的左右手互博,不好听就是放屁自己追着玩,小狗转圈咬自己的尾巴 SpinWait是一个结构体,并不是一个类. ...

  10. cpu_relax( )-----对自选循环等待(spin-wait loops)操作的优化【转】

    cpu_relax()-----对自选循环等待(spin-wait loops)操作的优化 转自:http://www.doc100.net/bugs/t/173547/index.html 在loc ...

随机推荐

  1. 教你如何快速定制 SpringBoot banner

    之前说过如何快速创建SpringBoot项目,不知道的同学可以查看之前的文章 5分钟学会如何创建spring boot项目. 为了让大家脱单,码哥简直费尽心思,今天这个技能或许可以让你脱单! 今天我们 ...

  2. bind--dns-docker---[nslookup/dig]

    [dig]  https://www.cnblogs.com/apexchu/p/6790241.html [dns resolution and revserse ]https://www.cnbl ...

  3. Error: unable to load xmlsec-openssl library

    yum install libxml2-devel xmlsec1-devel xmlsec1-openssl-devel libtool-ltdl-devel

  4. 通过phpMyAdmin优化mysql 数据库可能存在的问题

    通过phpMyAdmin优化mysql 数据库可能存在的问题   文章来源:外星人来地球 欢迎关注,有问题一起学习欢迎留言.评论

  5. MediaPlayer: 在不同控件之间实现视频的无缝切换的方法

    最近使用MediaPlayer + TextureView 实现了一个视频播放器,并且实现了它的横竖屏切换的效果,唯一美中不足的是在横竖屏切换的时候画面会卡顿一下,虽然也不影响播放,但是怕测试会报Bu ...

  6. python开发-实现redis中的发布订阅功能

    Python3学习(二十七):python实现Redis的订阅与发布(sub-pub机制) 先介绍一下redis的pub/sub功能: Pub/Sub功能(means Publish, Subscri ...

  7. WGS84 2 GCJ-02

    #include ; ) { x=-x; ff=; } cc=) ff=; ) ff=; } x=tt; ss=x; s2=x; tt=tt*tt; s2=s2*tt; ss=ss-s2* ) ss= ...

  8. java对list进行排序

    主要讲述对list进行排序的几种方式 1.先来个简单的,上代码 import java.util.ArrayList; import java.util.Collections; import jav ...

  9. (九)Centos之搜索命令whereis、which和字符串搜索命令grep

    一.Centos之命令搜索命令whereis与which 1.1 whereis 命令名(搜索命令所在路径及帮助文档所在位置) 选项: -b :只查找可执行文件位置 -m:只查找帮助文件 1.2 wh ...

  10. rdd的元素打印

    在集群上运行spark程序时,rdd的操作都在worker机上,因此输出rdd的元素将在worker机的标准输出上进行,驱动节点上不会运行,故直接才程序中写如下代码 rdd.foreach(print ...