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

时间:2014-07-08 11:27:20

标签: multithreading thread-sleep

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

下面的代码可以描述为"忙着等待#34 ;?尽管它使用Thread.Sleep()来允许上下文切换?

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

PS - 维基百科中忙碌等待的当前定义表明它是一种不那么浪费的"忙着等待的形式。

5 个答案:

答案 0 :(得分:7)

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

非忙碌等待是阻止呼叫。示例的非繁忙版本将涉及等待同步原语,例如事件或条件变量。例如,这个伪代码:

// 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调用块和线程。

答案 1 :(得分:6)

那不忙等待。忙碌的等待或旋转,恰恰相反:避免上下文切换。

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

很多更好的选择是使用SpinWait.SpinUntil

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

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


但旋转只有用如果你绝对确定你不会等待一个条件的时间超过处理器将上下文切换回来的时间。再次。即,不超过几微秒。

如果您想每隔毫秒轮询一个条件,那么您应该使用阻塞同步原语,如维基页面所示。对于您的场景,我建议使用AutoResetEvent,它会在调用WaitOne时阻止该线程,直到事件被发出信号(即条件成立)。

另请阅读:Overview of Synchronization Primitives

答案 2 :(得分:2)

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

为了批评这段代码,我会说这样的话:“如果睡眠太小而不允许核心在检查之间做有用的工作,这段代码可能会忙等待。应该改变它以便制作这段代码的代码需要做的工作触发了这种反应。“

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

答案 3 :(得分:0)

当代码处于休眠状态时,从技术上讲,它将处于休眠状态以释放CPU。在忙于等待期间,您的代码将占用CPU,直到满足条件为止。

  

下面的代码可以描述为“忙等待”吗?尽管它使用Thread.Sleep()进行上下文切换?

不是忙于等待,而是轮询比忙于等待更有效。两者之间有区别

简而言之,繁忙等待正在阻塞,而轮询未阻塞。

答案 4 :(得分:-1)

忙等待是这样的:

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

条件可以是“检查当前时间”(例如性能计数器轮询)。有了这个,您可以在线程中获得非常准确的暂停。这对于例如低级 I/O(切换 GPIO 等)很有用。因此,您的线程一直在运行,如果您使用协作多线程,则您完全可以控制线程将在等待您的情况下停留多长时间。通常这种线程设置了高优先级并且是不间断的。

现在非忙等待意味着线程是非忙的。它允许另一个线程执行,所以有一个上下文切换。为了允许上下文切换,在大多数语言和操作系统中,您可以简单地使用 sleep()。还有另一个类似的函数,如yield()、wait()、select()等。这取决于操作系统和语言,如果它们是非忙或忙实现的。但根据我的经验,在所有情况下,sleep > 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 几乎整个时间都处于 sleep(1) 状态。这将是一个非阻塞的 for 循环。但是如果wait()是一个繁重的计算函数,不允许线程上下文切换,那么整个for循环可能会变成一个阻塞函数,即使有sleep(1)。想想最坏的情况:wait() 函数永远不会返回给调用者,因为很长一段时间都没有满足条件。

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

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

如你所见,它是一样的:因为你不知道条件,你不能说等待是忙还是不忙。