在UNIX系统中调用sleep(0)的效果是什么?

10

在我所涉及到的一些重度多线程、多进程应用系统中,我看到人们会这样做。这似乎是在调试代码时使用的:

std::cerr << "DEBUG: Reaching: " << __FUNCTION__ << " @ " << __LINE__ << std::endl;
sleep(0);
如果我把 sleep(0); 宏定义为 ""(即将其更改为空),那么系统的调试输出似乎会以不同的顺序出现(不太可预测),因此我认为这使得该行代码更快地执行。但是,我认为 std::cerr 是无缓冲的,而且 std::endl 也会调用 std::flush(),所以这是为什么呢?

我本来想让你去读 man 手册,但我先读了一遍,发现它并没有回答这个问题。 - Keith Thompson
1
@Nemo,差不多了。这实际上是整个重量级进程的产出;控制权回到进程调度程序。 - Charlie Martin
1
@CharlieMartin,sleep() 只会影响当前线程,而不是整个进程。 - bdonlan
非常感谢大家 :) 我发现它正在放弃时间片,但我不确定它是否可靠以及操作系统如何处理它 - 我想有更好的选择,你们提到了 pthread_yield(),这似乎是个不错的选择。 - John Humphreys
5个回答

14

基本上,它会将控制权返回给调度程序,并让您立即重新安排。话虽如此,这基本上是一种试图欺骗操作系统执行某些操作的方法。

但是,欺骗操作系统从来都不是一个好主意。

如果系统负载适当,则进入睡眠状态将意味着操作系统获得控制并允许I/O队列刷新,因此它将产生该效果。有时候。取决于情况。

实际上它在做什么取决于具体实现的细节,而这些细节你无法依赖。


9

它以一种难以预测的方式干扰了调度程序。

通常结果类似于调用pthread_yield() - 你会放弃掉你的时间片。这样做的结果是,在大量调试输出负载下,你不太可能在写入cerr时被抢占(即,在那些<<之间),因为你将在最后一个调试输出后的时间片开始处,所以你不太可能出现多个线程彼此覆盖输出(即,得到像DEBUG: REACHING: foo() @ DEBUG: REACHING bar()17 @ 24这样的东西)。

尽管如此,这种方法是不可靠的 - 它是扰动调度程序,而不是要求特定语义。它也很慢 - 无条件地进入内核进行yield操作(可能会过分频繁地在多个线程之间跳转控制)。并且在多核CPU上它可能不太可能正常工作。

更好的方法是在所有这样的调试输出上放置互斥锁。尽管如此,由于这是调试代码,作者使用快速脏代码来使其工作足够好,以便调试他们遇到的任何问题,这并不奇怪。


1
在大多数平台上,sleep(0) 导致调度程序将当前线程视为已使用完其时间片。通常意味着如果可能的话,同一进程中的另一个线程将在该 CPU 核心上调度运行。正如您所指出的那样,它往往会使调度更加可预测。对我来说,在调试时似乎适得其反。
我同意 Nemo 的评论,它与 pthread_yield 差不多。它确实有一些合法的用途,但有时会错误地用作提供更公平或减少延迟的方式。通常情况下,它只会使性能变差,因为上下文切换会导致缓存失效。
我看了其他答案,我同意大家都感到困惑。我的赌注是除了某个人认为更可预测的调度是好事之外,没有什么好理由。(这不是。灵活性才是好的。您对调度程序施加的要求越多,它就必须牺牲更多来满足这些要求。如果您强制实施不必要的要求,您将仅为性能付出代价而已。)

0

你可以使用这种方式来让其他可能正在等待一些CPU时间的进程运行,但如果没有其他进程在等待运行,则不会强制进行特定的延迟。


0
如其他人所提到的,sleep(0)是一个系统调用,会导致当前线程放弃对CPU的控制。
我能想到唯一合理的使用情况是在实现自己的同步原语时(例如使用汇编指令如LOCK(x86)和LDREX和STREX(ARMv7)实现自旋锁)。在这种情况下,如果遇到锁,我们可能会立即放弃控制权,希望另一个线程会完成其操作并解锁我们的锁。这绝对是一直自旋直到我们的时间片结束。
话虽如此,当实现自己的同步原语时,您确实需要知道自己在做什么。具体而言,您可能需要添加内存屏障以强制读取和写入的顺序。最好使用平台上提供的原语。

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