完成代码:SpinWait.cs (dot.net)
原理:
SpinOnce()执行一次,执行次数超过10之后,每次进行自旋便会触发Thread.Yield()上下文切换的操作,在这之后每5次会进行一次sleep(0)操作,每20次会进行一次sleep(1)操作。SpinOnce()执行一次是大概7个时钟周期。第一自旋例外,第一次的时候比较耗时。
SpinOnce(int32 num) 执行num次后进入sleep(1); SpinOnce执行一次是大概7个时钟周期。第一例外,第一次的时候比较耗时。
Thread.Sleep(0) 表示让优先级高的线程插队,如果没有线程优先级更高线程插队,那么就继续执行。
Thread.Yield() 让有需要的线程先执行,忽略线程优先级(低级别的线程也可以在core运行),而该线程进入就绪队列。
SpinWait 内部用的是Thread.SpinWait(),Thread.SpinWait()调用外部函数(private static extern void SpinWaitInternal(int iterations);)现实自旋。
属性
count 表示自旋次数
isNextSpinWillYield :自旋超过10次,开始yield模式
源代码:
public bool NextSpinWillYield { get { if (_count < 10) { return Environment.IsSingleProcessor; } return true; } }
方法
Reset();将count设置未0。
SpinOnce()执行一次,在次数超过10之后,每次进行便会触发Thread.Yield()上下文切换的操作,在这之后每5次会进行一次sleep(0)操作,每20次会进行一次sleep(1)操作。
SpinOnce(int32 num) 执行num次后进入sleep(1);SpinOnce方法都 thread.spin(nm).
核心代码
internal const int YieldThreshold = 10; // When to switch over to a true yield. private const int Sleep0EveryHowManyYields = 5; // After how many yields should we Sleep(0)? internal const int DefaultSleep1Threshold = 20; // After how many yields should we Sleep(1) frequently? private void SpinOnceCore(int sleep1Threshold) { if (( _count >= YieldThreshold && ((_count >= sleep1Threshold && sleep1Threshold >= 0) || (_count - YieldThreshold) % 2 == 0) ) || Environment.IsSingleProcessor) { if (_count >= sleep1Threshold && sleep1Threshold >= 0) { Thread.Sleep(1); } else { int yieldsSoFar = _count >= YieldThreshold ? (_count - YieldThreshold) / 2 : _count; if ((yieldsSoFar % Sleep0EveryHowManyYields) == (Sleep0EveryHowManyYields - 1)) { Thread.Sleep(0); } else { Thread.Yield(); } } } else { int n = Thread.OptimalMaxSpinWaitsPerSpinIteration; if (_count <= 30 && (1 << _count) < n) { n = 1 << _count; } Thread.SpinWait(n); } // Finally, increment our spin counter. _count = (_count == int.MaxValue ? YieldThreshold : _count + 1); }
使用案例:
using System.Diagnostics; using System.Reflection; class Program { static SpinLock sl = new(); static void Main(string[] args) { Stopwatch stopwatch = new (); SpinWait sw = new(); //每次自旋的时间 for (int i = 0; i <40; i++) { // sw.Reset();// SpinWait内部是 采用count 累计计数的,所以每次使用都要清零 stopwatch.Reset(); stopwatch.Start(); sw.SpinOnce(31); stopwatch.Stop(); Console.WriteLine(stopwatch.ElapsedTicks+sw.NextSpinWillYield.ToString()+"count:"+sw.Count); } } }
SpinOnce()执行一次是大概7个时钟周期。第一例外,第一次的时候比较耗时。