sleep/nanosleep 是否通过使用忙等待方案来工作?

Does sleep/nanosleep work by utilizing a busy wait scheme?

我想知道 sleep/nanosleep 内部是如何实现的?考虑这段代码:

{ // on a thread other than main() thread
  while(1)
  {
    //do something
    sleep(1);
  }
}

CPU 是否会进行持续的上下文切换以检查是否完成 1 秒的睡眠(即内部忙等待)。

我怀疑它是这样工作的,效率太低了。但是它是如何工作的?

同样的问题也适用于 nanosleep。

注意:如果这是 implementation/OS 特定的,那么我如何才能实施不会导致持续上下文切换的更有效方案?

"I am wondering how is sleep/nanosleep internally implemented?"

没有一个实现,但是每个OS和POSIX兼容实现sleep()nanosleep() 的人在实际实现此功能的方式上是自由的。

因此,如果没有特定 OS/POSIX 库实现的更多上下文,询问它实际上是如何完成的是毫无用处的。

此处不保证准确实现,但您可以期待一些属性。

通常 sleep (3) 是非常不准确的,因为 Linux 'man sleep 3' 状态甚至可以使用 SIGALM(信号)来实现。所以这绝对与性能无关。这绝对与自旋锁无关,因此不能 CPU 密集。

nanosleep 是完全不同的动物,甚至可以使用自旋锁来实现。哪个更重要,至少在 Linux nanosleep man 在第 2 节中,它是系统调用,所以至少它应该包括切换到内核模式。你真的需要它的高分辨率吗?

更新

看到您的评论后,我确实推荐 select() 用法,因为 man select 3 指出:

   #include <stdio.h>
   #include <stdlib.h>
   #include <sys/time.h>
   #include <sys/types.h>
   #include <unistd.h>

   int
   main(void)
   {
       fd_set rfds;
       struct timeval tv;
       int retval;

       /* Watch stdin (fd 0) to see when it has input. */
       FD_ZERO(&rfds);
       FD_SET(0, &rfds);

       /* Wait up to five seconds. */
       tv.tv_sec = 5;
       tv.tv_usec = 0;

       retval = select(1, &rfds, NULL, NULL, &tv);
       /* Don't rely on the value of tv now! */

       if (retval == -1)
           perror("select()");
       else if (retval)
           printf("Data is available now.\n");
           /* FD_ISSET(0, &rfds) will be true. */
       else
           printf("No data within five seconds.\n");

       exit(EXIT_SUCCESS);
   }

如果您需要为某些事件在线程中休眠并且此事件可以链接到文件描述符,这是经过验证的机制。

sleepnanosleep的POSIX说明(强调我的)

The sleep() function shall cause the calling thread to be suspended from execution until either the number of realtime seconds specified by the argument seconds has elapsed or a signal is delivered to the calling thread and its action is to invoke a signal-catching function or to terminate the process. The suspension time may be longer than requested due to the scheduling of other activity by the system.

(来源:http://pubs.opengroup.org/onlinepubs/9699919799/functions/sleep.html。)

The nanosleep() function shall cause the current thread to be suspended from execution until either the time interval specified by the rqtp argument has elapsed or a signal is delivered to the calling thread, and its action is to invoke a signal-catching function or to terminate the process. The suspension time may be longer than requested because the argument value is rounded up to an integer multiple of the sleep resolution or because of the scheduling of other activity by the system. But, except for the case of being interrupted by a signal, the suspension time shall not be less than the time specified by rqtp, as measured by the system clock CLOCK_REALTIME.

(来源:http://pubs.opengroup.org/onlinepubs/9699919799/functions/nanosleep.html。)

我读到说符合 POSIX 的系统不能为 sleepnanosleep 使用忙循环。调用线程需要暂停执行。

实现 sleep()nanosleep() 的典型方法是将参数转换为 OS 调度程序使用的任何比例(同时向上舍入)并将当前时间添加到它形成一个"absolute wake up time";然后告诉调度程序在达到 "absolute wake up time" 之前不要给线程 CPU 时间。不涉及忙等待。

请注意,OS 调度程序使用的任何规模通常取决于可用的硬件 and/or 用于计时。它可以小于纳秒(例如 "TSC deadline mode" 中使用的 80x86 上的本地 APIC)或大至 100 毫秒。

另请注意,OS 保证延迟不会少于您要求的;但通常不能保证它不会更长,并且在某些情况下(例如,重负载系统上的低优先级线程)延迟可能比请求的大得多。例如,如果您要求休眠 123 纳秒,那么在调度程序决定它可以给您 CPU 时间之前您可能会休眠 2 毫秒,然后在调度程序实际给您 [=] 之前可能还需要 500 毫秒34=] 时间(例如因为其他线程正在使用 CPU)。

一些OSs可能会尝试减少这个"slept much longer than requested"问题,而一些OSs(例如为硬实时设计)可能会提供某种保证(有限制- 例如,受制于线程优先级)延迟到期和返回 CPU 之间的最短时间。为此,OS/kernel 会将参数转换为 OS 的调度程序使用的任何比例(同时向下舍入而不向上舍入)并且可能减去一小部分 "just in case";以便调度程序在请求的延迟到期之前(而不是之后)唤醒线程;然后当线程被给予 CPU 时间(在上下文切换到线程的成本之后,并且可能在预取线程保证使用的各种缓存行之后)内核将忙于短暂等待直到延迟实际上已经过期了。这允许内核将控制权交还给非常接近延迟到期的线程。

例如,如果你要求休眠 123 纳秒,那么调度程序可能不会给你 CPU 100 纳秒的时间,那么它可能会花费 10 纳秒切换到你的线程,然后它可能会忙等待剩余的 13 纳秒。即使在这种情况下(完成忙等待),它通常也不会忙等待整个延迟时间。但是,如果延迟非常短,内核只会做最后的忙等待。

最后,有一个特例值得一提。在 POSIX 系统上,sleep(0); 通常被滥用为 yield()。我不太确定这种做法的合法性 - 调度程序不可能支持 yield() 之类的东西,除非调度程序愿意浪费 CPU 时间做不重要的工作,而更重要的工作等待。