SpinWait与睡眠等待。使用哪一个?


75

它有效吗

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

超时10000ms

要么

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的所有收益可能都不是一个好的用法模式?这是一个有效的假设吗?

您喜欢哪种方法,为什么?还有另一种更好的方法吗?


更新-变得更加具体:

有没有办法让BlockingCollection Pulse在达到限制容量时成为休眠线程?我宁可避免像马克·格雷夫(Marc Gravel)所说的那样忙碌地等待。

Answers:


51

最好的办法是有一些机制来主动检测的东西变成真正的(而不是被动地轮询它已经成为真正的); 这可以是任何类型的等待句柄,也可以是Taskwith Wait,也可以event是您可以订阅以解除粘滞的。当然,如果您执行这种“等到发生某事”,那仍然不如简单地完成下一个工作作为callback那样高效,这意味着:您不需要使用线程来等待。TaskContinueWith这一点,或者你可以做在工作event时,它被炒鱿鱼。在event可能是最简单的方法,根据上下文。Task但是,已经提供了您在此处谈论的所有内容,包括“超时等待”和“回调”机制。

是的,旋转10秒不是很好。如果您想使用当前代码之类的东西,并且如果您有理由期望短暂的延迟,但又需要允许更长的延迟,例如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);
    }
}

在“放回集合”代码中:

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

2
谢谢,我喜欢你的回答,这确实很好。假设您正在通过BlockingCollection跟踪某些资源的使用。它们被使用(从集合中删除)并在再次可供重用时返回集合。仅当所有这些都返回到集合中时,才会发生关闭。除了忙于等待之外,您将如何发出信号以表明关机可以继续进行(即,收集再次充满)?
Anastasiosyal

@Anastasiosyal我将封装集合,并在包装​​满时让包装器做一些事情;实际上,我可能Monitor为此使用a (将添加示例)
Marc Gravell

在从BlockingQueue派生的'ObjectPool'类的析构函数中,逐一弹出BlockingCollection中的所有对象,并将其处置(由于这是C#,请将检索到的引用设置为nil),直到弹出的对象数等于池深。然后,所有池化的对象均已处置,因此您可以处置队列并从dtor返回以继续关闭序列。无需轮询/忙碌等待!您可能需要暂停一些时间,以使泄漏的对象最终引发异常(或其他异常)。
马丁·詹姆斯

@MartinJames-在大多数情况下,这是一个有效的注释。在这种情况下,集合的所有者没有被销毁,而是被返回到池中(认为会话关闭并且会话返回到池中)
Anastasiosyal 2012年

2
@Anastasiosyal,我很抱歉,但是Monitor.Wait-没有:不会死锁;这是将监视器用作信号的正常且常用的技术。Wait释放持续时间的锁,然后在返回true或false之前重新获取锁。
Marc Gravell

130

在.NET 4中SpinWait,在屈服之前,会执行10次迭代的CPU密集型旋转。但是,在每个周期之后,它都不会立即返回给调用者。相反,它调用Thread.SpinWait通过CLR(本质上是OS)旋转一定的时间。该时间段最初为数十纳秒,但每次迭代都会翻倍,直到完成10次迭代为止。这可以使自旋(CPU密集型)花费的总时间变得清晰/可预测,系统可以根据条件(内核数等)进行调整。如果SpinWait在旋转生成阶段停留的时间过长,它将定期休眠以允许其他线程继续运行(有关更多信息,请参见J. Albahari的博客)。确保此过程使核心保持忙碌...

因此,SpinWait将CPU密集型旋转限制为固定的迭代次数,此后,它会在每次旋转时生成时间片(通过实际调用Thread.YieldThread.Sleep),从而降低了资源消耗。它还会检测用户是否正在运行一台单核计算机,并且在每个周期都屈服。

随着Thread.Sleep线程被阻塞。但是就CPU而言,此过程不会比上述过程昂贵。


这是从Albahari的网站复制的。应该参考一下!
georgiosd

1
它不是逐字复制的,而是受他网站的影响,因此为什么我引用了他的博客。
MoonKnight

1
感谢您的解释,它的确提供了很多信息。
坎比T
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.