SpinWait和Sleep等待,哪个更好?

82

将一个数组排序并搜索它,比在未排序的数组中进行搜索更有效吗?

SpinWait.SpinUntil(() => myPredicate(), 10000)

超时时间为10000毫秒

或者

使用Thread.Sleep轮询相同的条件是否更加高效?例如以下类似的SleepWait函数:

public bool SleepWait(int timeOut)
{
    Stopwatch stopwatch = new Stopwatch(); 
    stopwatch.Start();
    while (!myPredicate() && stopwatch.ElapsedMilliseconds < timeOut)
    {
       Thread.Sleep(50)
    }  
    return myPredicate()
}

我担心如果我们讨论的超时时间超过1秒,SpinWait的所有yielding可能不是一个好的使用模式?这个假设是否正确?

你更喜欢哪种方法?为什么?还有其他更好的方法吗?


更新 - 变得更加具体:

有没有一种方法可以让BlockingCollection在达到有限容量时脉冲睡眠线程? 我情愿完全避免忙等待,就像Marc Gravel建议的那样。

2个回答

137
在.NET 4中,SpinWait在进行10次自旋后才会暂停。但是,在每个周期结束后,它并不会立即返回给调用者相反,它通过CLR(本质上是操作系统)调用Thread.SpinWait以进行自旋,并且有一个固定的时间段。这个时间段最初只有几十纳秒,但每次迭代时都会加倍,直到完成10次迭代。这样可以使CPU密集型自旋期间的总耗时更加清晰/可预测,系统可以根据条件(核心数等)进行调整。如果SpinWait在自旋让渡阶段花费过长的时间,它会定期休眠以允许其他线程继续进行(有关更多信息,请参见J. Albahari的博客)。此过程保证了一个核心忙碌......

因此,SpinWait将CPU密集型自旋限制为一组迭代之后,会在每次自旋时释放其时间片(实际上通过调用Thread.YieldThread.Sleep),从而降低其资源消耗。它还会检测用户是否在运行单核机器,并在这种情况下在每个周期进行让渡。

使用Thread.Sleep会阻塞线程。但是,与上述过程相比,这个过程在CPU方面不那么昂贵。


这是从Albahari的网站复制的。应该进行引用! - georgiosd
4
这并不是逐字复制,但受到了他的网站的影响,因此我引用了他的博客。 - MoonKnight
1
感谢您的解释,非常有启发性。 - T Kambi

56

最好的方法是有一些机制来主动检测事情是否成为真(而不是被动地轮询它是否已经成为真);这可以是任何类型的等待句柄,或者可能是一个使用WaitTask,或者可能是一个您可以订阅以解除阻塞的event。当然,如果您做了那种“等待直到某些事情发生”的事情,那仍然不如简单地将下一步工作作为回调函数完成,这意味着:您不需要使用线程进行等待。Task已经提供了ContinueWith来实现这个功能,或者在触发事件时可以直接在事件中完成工作。根据上下文,event可能是最简单的方法。但是,Task已经提供了您在此处讨论的大多数功能,包括“带超时的等待”和“回调”机制。

如果您想要像当前代码一样使用某些东西,并且有理由期望短延迟但需要允许更长时间 - 可以使用SpinWait进行(例如)20毫秒的自旋,然后使用Sleep进行剩余时间的等待。


关于评论;以下是我如何连接“是否已满”的机制:

private readonly object syncLock = new object();
public bool WaitUntilFull(int timeout) {
    if(CollectionIsFull) return true; // I'm assuming we can call this safely
    lock(syncLock) {
        if(CollectionIsFull) return true;
        return Monitor.Wait(syncLock, timeout);
    }
}

在“放回到集合中”的代码中,使用了 with 语句:

if(CollectionIsFull) {
    lock(syncLock) {
        if(CollectionIsFull) { // double-check with the lock
            Monitor.PulseAll(syncLock);
        }
    }
}

2
谢谢,我喜欢你的回答,这确实很好。假设您正在通过BlockingCollection跟踪某些资源的使用情况。它们被使用(并从集合中删除),当它们再次可用于重用时,它们会被返回到集合中。只有当所有这些都返回到集合中时,才能发生关闭。除了忙等待之外,您如何发出可以继续进行关闭的信号(即集合再次完整)? - Anastasiosyal
@Anastasiosyal 我会封装这个集合,并让包装器在满时执行某些操作;实际上,我可能会使用 Monitor 来实现这一点(将添加示例) - Marc Gravell
在从BlockingQueue派生的'ObjectPool'类的析构函数中,逐个从BlockingCollection中弹出所有对象并处置它们(由于这是C#,将检索到的引用设置为nil),直到弹出的对象数等于池的深度。然后已处置所有池化对象,因此可以处置队列并从dtor返回以继续关闭序列。无需轮询/忙等待!您可能希望在获取上设置一些超时,以便泄漏的对象最终引发异常(或其他操作)。 - Martin James
@Anastasiosyal - 好的,那么你可以将一个名为 'OnReleased(object thisObject)' 的委托作为析构函数参数传递。当对象被检索时,该委托将被调用,并且可以将会话对象返回到它们来自的地方。或者,可以在构造函数参数中传递一个合适的委托,并将其存储在数据成员中,直到销毁时间。 - Martin James
2
@Anastasiosyal 我很抱歉 - 应该是 Monitor.Wait - 但不会出现死锁;这是使用监视器作为信号的正常和广泛使用的技术。Wait 在持续时间内释放锁,然后在返回 true 或 false 之前重新获取锁。 - Marc Gravell
显示剩余3条评论

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