如果我Thread.Sleep(),它真的很忙吗?

本文关键字:真的 很忙 Thread Sleep 如果 | 更新日期: 2023-09-27 18:11:51

我的问题对定义有点挑剔:

下面的代码可以被描述为"正在等待"吗?尽管它使用Thread.Sleep()来允许上下文切换?

while (true) {
    if (work_is_ready){
        doWork();
    }
    Thread.Sleep(A_FEW_MILLISECONDS);
}

PS -目前维基百科对忙碌等待的定义表明,这是一种"浪费较少"的忙碌等待形式。

如果我Thread.Sleep(),它真的很忙吗?

任何轮询循环,无论轮询操作之间的时间如何,都是一个忙等待。当然,睡几毫秒比根本不睡要"忙"得多,但它仍然涉及处理:线程上下文切换和一些最小的条件检查。

非忙等待为阻塞呼叫。示例的非繁忙版本将涉及等待同步原语,例如事件或条件变量。例如,以下伪代码:

// initialize an event to be set when work is ready
Event word_is_ready;
work_is_ready.Reset();
// in code that processes work items
while (true)
{
    work_is_ready.Wait();  // non-busy wait for work item
    do_work();
}

这里的区别在于没有周期性轮询。Wait调用阻塞,并且在设置事件之前不会调度线程。

等待不算忙。忙等待,或旋转,涉及到相反的情况:避免上下文切换。

如果你想允许其他线程运行,当且仅当其他线程准备好运行时,为了避免单线程cpu的死锁场景(例如,当前线程需要将work_is_ready设置为true,但如果这个线程不放弃处理器并让其他线程运行,它永远不会设置为true),你可以使用Thread.Sleep(0)

一个更好的选择是使用SpinWait.SpinUntil

SpinWait.SpinUntil(() => work_is_ready);
doWork();

SpinWait发出一个特殊的rep; nop(重复no-op)或pause指令,让处理器知道您正在忙着等待,并且针对超线程cpu进行了优化。此外,在单核cpu中,这将立即yield处理器(因为如果只有一个核心,那么繁忙等待是完全无用的)。


但是旋转只有在你绝对确定等待一个条件的时间不会超过处理器切换上下文的时间时才有用。也就是说,不超过几微秒。

如果您希望每隔几毫秒轮询一次条件,那么您应该使用阻塞同步原语,正如wiki页面所建议的那样。对于您的场景,我建议使用AutoResetEvent,它在调用WaitOne时阻塞线程,直到事件被发出信号(即,条件变为真)。

另请参阅:同步原语概述

这取决于操作系统和您正在睡眠的确切毫秒数。如果睡眠足够长,操作系统可以切换到另一个任务,填充其缓存,并有效地运行该任务,直到您的任务准备好再次运行,那么它就不会忙于等待。如果不是,那么就是。

要批评这段代码,我会这样说:"如果睡眠太少,内核无法在检查之间做有用的工作,这段代码可能会忙等待。应该对其进行更改,以便使该代码需要执行工作的代码触发该响应。"

这个糟糕的设计产生了一个不必要的设计问题——睡眠应该多长?如果时间太短,你就忙着等。如果时间太长,工作就不会完成。即使时间足够长,您不需要忙着等待,也会强制进行不必要的上下文切换。

当您的代码处于睡眠状态时,从技术上讲,它将处于睡眠状态,从而释放一个CPU。在繁忙的等待中,代码会占用CPU,直到满足条件。

下面的代码可以被描述为"正在等待"吗?尽管它使用Thread.Sleep()来允许上下文切换?

不是忙等待,而是轮询,比忙等待性能更高。

简单地说,忙等待是阻塞,而轮询是非阻塞。

忙碌的等待是这样的:

for(;;) {
  if (condition) {
      break;
  }
}

条件可以是"检查当前时间"。(例如性能计数器轮询)。有了这个,你可以得到一个非常准确的暂停在你的线程。这对于低级别I/O(切换gpio等)非常有用。正因为如此,你的线程一直在运行,如果你使用的是协作多线程,那么你就完全控制了线程在等待你的过程中停留的时间。通常这类线程具有高优先级集,并且是不可中断的。

现在是非忙等待意味着线程是非忙的。它允许其他线程执行,因此存在上下文切换。为了允许上下文切换,在大多数语言和操作系统中,您可以简单地使用sleep()。还有其他类似的函数,如yield()、wait()、select()等。这取决于操作系统和语言,如果它们是非繁忙或繁忙实现。但根据我的经验,在所有情况下,睡眠>0总是空闲的

非繁忙等待的优点是允许其他线程运行,其中包括空闲线程。有了这个,你的CPU可以进入省电模式,时钟关闭等。它还可以运行其他任务。在指定的时间之后,调度器尝试返回到您的线程。但这只是一个尝试。它不是精确的,它可能比你的睡眠定义的要长一点。

我认为

。现在清楚了。

现在最大的问题是:这是忙还是不忙的等待:

for(;;) {
  if (condition) {
      break;
  }
  sleep(1);
}

答案是:这是一个非忙等待。Sleep(1)允许线程执行上下文切换。

现在下一个问题:第二个for()是忙的还是非忙的等待:

function wait() {
  for(;;) {
    if (condition) {
        break;
    }
  }
}
for(;;) {
  wait();
  if (condition) {
    break;
  }
  sleep(1);
}

这很难说。它取决于wait()函数的实际执行时间。如果它什么都不做,那么CPU几乎整个时间都处于睡眠状态(1)。这是一个非阻塞的for循环。但是,如果wait()是一个不允许线程上下文切换的繁重计算函数,那么即使存在睡眠(1),整个for循环也可能成为阻塞函数。想想最坏的情况:wait()函数永远不会返回给调用者,因为条件很长时间没有达到。

这个很难回答,因为我们不知道条件。你可以想象这个问题,你不能回答这个问题,因为你不知道条件,用以下方法:

if (unkonwnCondition) {
  for(;;) {
    if (condition) {
        break;
    }
  }
} else {
  for(;;) {
    if (condition) {
        break;
    }
    sleep(1);
  }
}

正如你所看到的,这是一样的:因为你不知道条件,所以你不能说等待是忙还是不忙。