Thread.SpinWait方法的目的是什么?

61

MSDN上看,它的目的并不是很清楚。

它能用来模拟强大的 CPU 计算测试吗?

5个回答

112

它被用作短期睡眠调用的替代品。

当您进行多线程锁定时,如果您要获取的资源已经被锁定,通常会进入睡眠状态并等待其变为可用。这样做时,您放弃了调度程序分配给您的其余时间,以便其他人可以使用处理器。 通常情况下,这是可以接受的,特别是对于长时间等待,比如等待IO,当您等待磁盘旋转时,许多其他进程可以在CPU上运行。

然而,有时候,您只需要等待很短的时间。在这种情况下,您通常会放弃剩余时间并等待所有其他线程完成工作,然后再获取机会。因此,您可以采取一种欺骗性的策略,而不是等待,您以“我们快到了吗?”的方式持续地轮询。如果锁仅保持了您剩余时间的一小部分,那么这将成为一种非常有效的等待方式,同时也非常高效,因为如果您正常等待,调度程序不必参与重新安排所有其他线程来使用您放弃的时间。

显然,如果每次想要锁定时都进行自旋,您将不会受到欢迎,您的应用程序将变得缓慢并且使用100%的CPU,但是在正确的时间以非常小的量使用它,则可以使应用程序更加响应。

如果您现在想“何时使用它?”,那么这是一个棘手的问题-如果您有一个经常被快速锁定和解锁的资源,那么在其周围使用自旋锁而不是等待是一个好主意(然后测试您的应用程序的性能),如果您尝试自旋一小段时间,然后回到正常的等待,也是一个合理的方法。但通常情况下,您永远不需要使用它。


假设我们想要平均延迟50毫秒,我们需要指定多少次迭代? - Charles Okwuagwu
3
SpinWait 很少会导致CPU利用率达到100%而使你卡住。这是因为如果它旋转的时间太长,它会开始让线程 yieldsleep。具体来说,如果旋转次数大于或等于10,则每19次旋转就会 sleep(1),每4次旋转就会 sleep(0),并且仅当下一次旋转确定具有让出潜力时才会 yield。在多处理器系统上,它可以在任何时候 sleepyield,不需要等待最初的10次旋转。在单处理器系统上,只有在旋转计数小于或等于10时,它才会 sleepyield - Michael J. Gray
4
你混淆了 Thread.SpinWait()(问题和答案)与 var sw = new SpinWait(); sw.SpinOnce();,后者会在内部自旋计数达到10次。 - DeepSpace101
3
如果只需要暂停数十毫秒,最好使用简单的 "thread.sleep" 方法。 - Stephen Kennedy
@StephenKennedy "几十毫秒"的情况下,你绝对会(而不仅仅是可能)使用简单的thread.sleep。现代处理器可以在单个核心上执行数百万条指令,只需要几十毫秒的时间。 - John Pankowicz
显示剩余2条评论

78

目的是在您认为正在等待的条件非常非常快就会成立时进行“廉价”等待。通常情况下,如果您正在等待某些事情,您会让线程进入睡眠状态,处理器/操作系统将切换到另一个线程。上下文切换并不特别廉价,因此,如果您对情况有深入了解并且相信等待比上下文切换更便宜,则可以自旋等待。

我的建议是:如果您需要询问,那么您不需要使用它。 (我自己从未想要过。)基本上,这是一种在非常少数情况下非常有用的东西,但大多数人应该远离它。


3
我猜这句话属于“除非你测量并发现有问题的整个范畴…” - jerryjvl
@jon skeet,所以您默认建议仅使用lock(obj),只有在通过基准测试确定它对于您的情况更快时才使用spinwait? - rollsch
@rolls:是的,绝对没错。这是一个非常专业的功能。 - Jon Skeet

16

顺便提一下,自从Windows 7以来,微软已经摆脱了线程调度程序自旋锁机制,因为它在多核CPU上的扩展性不好。请看this:

编辑: 由于Channel9不再存在(在我看来是个愚蠢的决定),我发布的链接已经失效。您可以在YouTube上找到该视频。


2
它说他们已经摆脱了Windows中使用的一个自旋锁——调度程序自旋锁。 - Kevin Doyon

6
据我所知(如有错误请指正!),旋转等待的唯一用途是实现锁定或线程间回调机制。通常情况下,这两种机制不应该手动实现,因为它们已经存在。
当你锁定了一个资源并且另一个线程请求同步访问它时,它基本上必须等待第一个线程完成使用。这种等待可以通过简单地在循环中旋转(或通过Jon提到的睡眠+上下文切换)来实现。

2
在游戏引擎的Render()循环内使用异常来限制帧率。其他任何Sleep()机制都无法提供足够好的保证,确保你会在何时被切换回来。 - Basic
2
@Basic True,尽管通常做法是跳过渲染周期中的循环等待,而不是使用显式(嵌套)等待循环。当然,效果是相同的。 - Konrad Rudolph

0
如果您需要精确的睡眠时间,就不能使用Thread.Sleep(),因为它非常不准确(取决于CPU负载,误差可达±25毫秒),因为Windows是一个多任务操作系统。我使用以下代码来计时相关和短暂的睡眠间隔,仅为几毫秒。
// IMPORTANT:
// Use this only for very short sleep intervals. 
// One CPU core will run with 100% load during this interval.
public static void SleepPrecise(int s32_Interval) // in ms
{
    if (s32_Interval <= 0)
        return;

    // System.Diagnostics.Stopwatch uses the performance counter in the processor 
    // which has a precision of micro seconds or even nano seconds.
    Stopwatch i_Watch = new Stopwatch();
    i_Watch.Start();

    while (i_Watch.ElapsedMilliseconds < s32_Interval)
    {
        // SpinWait(80000) --> 1 ms on a 3.6 GHz AMD Radeon processor
        Thread.SpinWait(5000);
    }
}

如果您需要极高的时间精度,您还可以:

Thread.CurrentThread.Priority = ThreadPriority.Highest;
try
{
   .... execute your timing relevant code
}
finally
{
    Thread.CurrentThread.Priority = ThreadPriority.Normal;
}

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接