【问题标题】:Is there an alternative for sleep() in C?在 C 中是否有 sleep() 的替代方法?
【发布时间】:2010-09-20 19:56:33
【问题描述】:

在传统的嵌入式编程中,我们会给出这样的延迟函数:

for(i=0;i<255;i++)
   for(j=0;j<255;j++);

在微处理器看来,这就是 sleep() 函数的工作原理吗?

C 中的 sleep() 函数有替代方案吗?

【问题讨论】:

  • 在我的环境中可以是 windows 桌面机器和 turbo c 编译器,那么对于桌面环境,sleep() 有什么替代方案吗?(不适用于嵌入式系统)
  • 为什么需要替代睡眠?传统上,睡眠将以不需要像您描述的那样进行繁忙循环的方式实现。相反,线程根本不会被调度。

标签: c sleep delay


【解决方案1】:

您描述的这种循环称为“忙等待”。在实际操作系统中,休眠不会导致忙等待;它告诉操作系统在睡眠期结束之前不要安排进程。

【讨论】:

  • 两者都有。例如,Windows 内核 KeStallExecutionProcessor 处于忙等待状态,而 KeDelayExecutionThread 处于非忙等待状态。有一种情况是你想在不丢失上下文的情况下等待,例如重置硬件->等待几个微->做其余的硬件初始化
  • 我同意在某些情况下忙等待确实是合适的并且在内核中实现。例如,自旋锁定。所以我想我的答案应该是,“在真正的操作系统中,睡眠通常不会导致忙碌的等待”。 :-)
【解决方案2】:

一种常见的机制是使用保证超时的select(),并将休眠时间指定为超时:

// Sleep for 1.5 sec
struct timeval tv;
tv.tv_sec = 1;
tv.tv_usec = 500000;
select(0, NULL, NULL, NULL, &tv);

select() 通常用于检查一组文件描述符并等待至少一个准备好执行 I/O。如果没有准备好(或者,在这种情况下,如果没有指定 fds),它将超时。

select() 相对于忙循环的优势在于它在睡眠时消耗的资源非常少,而忙循环在其优先级允许的范围内独占处理器。

【讨论】:

  • 可能是过度设计,但我认为 select() 可以被信号中断。如果在 1/2 秒后中断会发生什么?
  • 很好——在这种情况下,使用 pselect() 来屏蔽信号,或者创建自己的睡眠感知信号处理程序。谢谢!
  • 但是写here(那里点击等待IO)select是不可中断的。 select的问题解释here
【解决方案3】:

替代方案取决于您要做什么以及您使用的操作系统。

如果您只是想浪费时间,那么这些可能会有所帮助:

在大多数 unix 类型的系统上,您会发现一个“睡眠”功能,它或多或少类似于具有更高分辨率的睡眠。小心那个,因为它通常不能只睡一微秒。

在某些 unix 类型的系统上,选择系统调用可用于所有文件描述符设置为零的情况下,以获得相当准确的亚秒级等待。

在 Windows 系统上,您可以使用 Sleep,这几乎相同,但需要几毫秒。

在多任务操作系统中,有时可以将睡眠函数设为 0 作为参数。这通常会导致函数放弃它的时间片,但如果没有其他任务准备好运行,则会立即重新调度。

【讨论】:

    【解决方案4】:

    您不会使用您发布的代码在嵌入式系统上休眠。一个像样的编译器会完全删除它,即使你的编译器没有删除它也是次优的,因为在紧密循环中运行处理器会消耗功率,这对于嵌入式系统来说是一个问题。即使是不依靠电池运行的系统也会关心电力使用情况,因为更低的电力使用意味着更便宜的电源和冷却系统。

    您通常执行此操作的方式是您的 CPU 将执行某种 IDLE 或 SLEEP 指令,这将导致它暂时停止处理命令。连接到定时器电路的外部中断线将定期唤醒处理器,CPU 会在该点检查它是否已经睡了足够长的时间,如果没有,它会重新进入睡眠状态。

    //Pseudo code
    int start = getTime();
    int end = start + sleepTime;
    
    while (getTime() < end) {
           asm("SLEEP");
    }
    

    具体细节因处理器而异。如果您在操作系统上作为进程运行,则 sleep 调用通常只是告诉调度程序暂停您的进程,然后内核决定是调度另一个进程还是使 CPU 休眠。此外,上面的代码不适用于需要截止日期保证等的实时系统。在这些情况下,您需要获取循环中的时间,知道时间中断的持续时间,以便您知道是否可以重新睡眠超过最后期限,并可能重新编程计时器硬件或忙于等待。

    【讨论】:

    • 如果 end
    • 伪代码,我可以忽略这些细节,假设一个无限大小的 int ;-)
    【解决方案5】:

    您在 OP 中谈论“嵌入式编程”。如果你在做嵌入式工作并且需要像 sleep() 这样的东西,通常有可用的硬件计数器/定时器。这会因架构而异,因此请查看数据表。

    如果您不从事嵌入式工作,我深表歉意 :)

    【讨论】:

      【解决方案6】:

      如果您使用 for 循环,您最好知道它们编译成什么以及这些指令在您给定的时钟速度下需要多长时间,确保 CPU 运行您的指令而不是其他任何东西(这可以在嵌入式系统中完成,但很棘手,因为它不允许中断)。

      否则,您将无法判断实际需要多长时间。

      早期的 PC 游戏存在这个问题 - 它们是为 4.7MHz 的 PC 构建的,当更快的计算机出现时,它们就无法玩了。

      “睡眠”工作的最佳方式是让 CPU 知道在任何给定点的时间。不一定是实际时间(上午 7 点 15 分),但至少是相对时间(从某个时间点算起 8612 秒)。

      这样它可以对当前时间应用一个增量,并在循环中等待,直到达到当前+增量。

      任何依赖于 CPU 周期数的东西本质上都是不可靠的,因为 CPU 可能会转到另一个任务并让你的循环挂起。

      假设您有一个内存映射的 16 位 I/O 端口,CPU 每秒递增一次。我们还假设它位于嵌入式系统中的内存位置 0x33,其中 int 也是 16 位。然后一个叫做 sleep 的函数变成这样:

      void sleep (unsigned int delay) {
          unsigned int target = peek(0x33) + delay;
          while (peek(0x33) != target);
      }
      

      您必须确保 peek() 每次都返回内存内容(因此优化编译器不会弄乱逻辑)并且您的 while 语句每秒运行一次以上,这样您就不会错过目标,但这些都是运营问题,不会影响我所介绍的概念。

      【讨论】:

      • 我猜你会喜欢while (peek(0x33) &lt; target);,或者当(出于某种原因)硬件跳过你感兴趣的确切值时,你会有一个有趣的调试体验。
      【解决方案7】:

      关于 sleep() 如何工作的更多信息here

      顺便说一句,忙碌的等待不一定适合业余爱好者——尽管它确实会烧毁您可能想用于其他目的的处理器。如果您使用的是时间源,则您受限于该源的粒度。例如。如果你有一个 1 ms 的计时器,并且想要 500 uS,你就有问题了。如果您的嵌入式系统可以处理您将在 500 uSec 的循环中嗡嗡作响的事实,那可能是可以接受的。即使您有一个具有所需粒度的计时器,您也需要在正确的时间从该计时器中获得一个中断......然后调度中断处理程序......然后进入您的代码。有时,繁忙的循环是最方便的解决方案。 有时。

      【讨论】:

        【解决方案8】:

        即使在嵌入式系统中,忙等待也适用于业余爱好者,请使用实时源。

        【讨论】:

          【解决方案9】:

          任何体面的 C 编译器无需额外工作即可完全删除您的代码,延迟就会消失

          【讨论】:

          • 我记得很多年前的一些基准测试,其中特定的编译器生成的可执行文件比其他编译器好几个数量级。结果是编译器意识到循环的结果没有在其他任何地方使用,所以它优化了整个循环不存在:-)。
          【解决方案10】:

          sleep 实际上与操作系统接口,其中睡眠进程被放置在调度队列之外。我通常使用:

          poll(0, 0, milliseconds);
          

          适用于 POSIX 兼容系统。 select 也适用于 Windows(它们必须有一个原生 API(可能称为 Sleep)。)

          【讨论】:

          • 我们在 linux 2.6.11.6 中发现了一个 bug,它导致 poll 在某些情况下会挂起整个系统。切换到 select() 解决了问题;不知道最近的内核中是否还存在。
          【解决方案11】:

          Joseph Albahari 在“C# 中的线程”中的第 20 页对此进行了有趣的讨论。您在 .Net 中的睡眠时间不能少于 1 毫秒,但 DateTime.Ticks 的间隔粒度为 100 纳秒(= 0.1 微秒)。为了控制我的 5 轴 CNC 步进器,我只需要在步进命令之间暂停 10 微秒。我使用微控制器进行讨厌的循环,但我认为如果你有一大堆工作,交出一个处理器来完成这项工作是可以的,尽可能停止线程。至少它不会总是一样的。

          【讨论】:

          • 在 Windows 10 中睡眠时间不能少于 16 毫秒
          【解决方案12】:

          在 Linux 中可用 睡眠(int 微秒) nanosleep( ... ) 更精确请参阅调用参数的手册页

          【讨论】:

            【解决方案13】:
            #include <Windows.h>
            
            static NTSTATUS(__stdcall *NtDelayExecution)(BOOL Alertable, PLARGE_INTEGER DelayInterval) = (NTSTATUS(__stdcall*)(BOOL, PLARGE_INTEGER)) GetProcAddress(GetModuleHandle("ntdll.dll"), "NtDelayExecution");
            
            static NTSTATUS(__stdcall *ZwSetTimerResolution)(IN ULONG RequestedResolution, IN BOOLEAN Set, OUT PULONG ActualResolution) = (NTSTATUS(__stdcall*)(ULONG, BOOLEAN, PULONG)) GetProcAddress(GetModuleHandle("ntdll.dll"), "ZwSetTimerResolution");
            
            
            
            
            static void SleepShort(float milliseconds) {
                static bool once = true;
                if (once) {
                    ULONG actualResolution;
                    ZwSetTimerResolution(1, true, &actualResolution);
                    once = false;
                }
            
                LARGE_INTEGER interval;
                interval.QuadPart = -1 * (int)(milliseconds * 10000.0f);
                NtDelayExecution(false, &interval);
            }
            

            【讨论】:

            • ZwSetTimerResolution 更改线程或整个系统的分辨率?
            【解决方案14】:

            在 unix 衍生操作系统中,您可能会安排一个 signal() 调用,并且您的代码会简单地阻塞代码,直到发出信号。信号是为此目的而设计的,它们非常简单有效。

            【讨论】:

            • 信号一般不用于延迟目的,尽管它们可以用于此目的。它们不是最低开销的机制,因为它们是为异步中断设置的。上面描述的 select() 替代方法可能更有效。
            【解决方案15】:

            尝试......真正解决这个问题,即有效的东西(不像上面的回答尝试)lol

            我仍然需要改进这段代码才能让它出来。很少有插件是受欢迎的。

            // Sleep for both Windows and Linux: 
            // Too bad? No one proposed you a solution that works? 
            // Since Windows has no select.h nor poll.h, an implementation
            // is necessary.
            //  
            // Solutions on boards are often refered to use either select or poll, but ok, what about C (not c++)?
            //
            /// implementation of poll is destined in this attempt for windows
            /// Ideally, you add this part of code to the header of you *.c file, and it might work through...
            
            #ifdef WIN32
            #include <time.h>
            #include <sys/time.h>
            #include <ws2tcpip.h>
            #include <Winsock2.h>
            #include <windows.h>
            /* winsock doesn't feature poll(), so there is a version implemented
             * in terms of select() in mingw.c. The following definitions
             * are copied from linux man pages. A poll() macro is defined to
             * call the version in mingw.c.
             */
            #define POLLIN      0x0001    /* There is data to read */
            #define POLLPRI     0x0002    /* There is urgent data to read */
            #define POLLOUT     0x0004    /* Writing now will not block */
            #define POLLERR     0x0008    /* Error condition */
            #define POLLHUP     0x0010    /* Hung up */
            #define POLLNVAL    0x0020    /* Invalid request: fd not open */
            struct pollfd {
              SOCKET fd;        /* file descriptor */
              short events;     /* requested events */
              short revents;    /* returned events */
            };
            
            int mingw_poll (struct pollfd *, unsigned int, int);
            
            #define poll(x, y, z)        mingw_poll(x, y, z)
            #endif
            
            
            
            
            
            int mingw_poll(struct pollfd *fds, unsigned int nfds, int timo)
            {
                struct timeval timeout, *toptr;
                fd_set ifds, ofds, efds, *ip, *op;
                int i, rc;
            
                /* Set up the file-descriptor sets in ifds, ofds and efds. */
                FD_ZERO(&ifds);
                FD_ZERO(&ofds);
                FD_ZERO(&efds);
                for (i = 0, op = ip = 0; i < nfds; ++i) {
                fds[i].revents = 0;
                if(fds[i].events & (POLLIN|POLLPRI)) {
                    ip = &ifds;
                    FD_SET(fds[i].fd, ip);
                }
                if(fds[i].events & POLLOUT) {
                    op = &ofds;
                    FD_SET(fds[i].fd, op);
                }
                FD_SET(fds[i].fd, &efds);
                } 
            
                /* Set up the timeval structure for the timeout parameter */
                if(timo < 0) {
                toptr = 0;
                } else {
                toptr = &timeout;
                timeout.tv_sec = timo / 1000;
                timeout.tv_usec = (timo - timeout.tv_sec * 1000) * 1000;
                }
            
            #ifdef DEBUG_POLL
                printf("Entering select() sec=%ld usec=%ld ip=%lx op=%lx\n",
                       (long)timeout.tv_sec, (long)timeout.tv_usec, (long)ip, (long)op);
            #endif
                rc = select(0, ip, op, &efds, toptr);
            #ifdef DEBUG_POLL
                printf("Exiting select rc=%d\n", rc);
            #endif
            
                if(rc <= 0)
                return rc;
            
                if(rc > 0) {
                    for (i = 0; i < nfds; ++i) {
                        int fd = fds[i].fd;
                    if(fds[i].events & (POLLIN|POLLPRI) && FD_ISSET(fd, &ifds))
                        fds[i].revents |= POLLIN;
                    if(fds[i].events & POLLOUT && FD_ISSET(fd, &ofds))
                        fds[i].revents |= POLLOUT;
                    if(FD_ISSET(fd, &efds))
                        /* Some error was detected ... should be some way to know. */
                        fds[i].revents |= POLLHUP;
            #ifdef DEBUG_POLL
                    printf("%d %d %d revent = %x\n", 
                            FD_ISSET(fd, &ifds), FD_ISSET(fd, &ofds), FD_ISSET(fd, &efds), 
                            fds[i].revents
                    );
            #endif
                    }
                }
                return rc;
            }
            

            【讨论】:

            • 我认为这个答案偏离了原始问题的轨道。这看起来更像是 select() 的跨平台解决方案,并且涉及轮询。真正的操作系统睡眠不会导致进程轮询。
            【解决方案16】:

            我在这篇文章 (http://cboard.cprogramming.com/c-programming/111229-how-use-sleep-function.html) 中找到了这个功能,它可以工作:

            #include <stdio.h>
            #include <windows.h>
            
            int main()
            {
                puts("Hello \n");
                /* in windows.h is declared the Sleep (upper S) function and it takes time in 
            miliseconds */
                Sleep(3000);
                puts("World \n");
                return 0;
            }
            

            【讨论】:

              猜你喜欢
              • 1970-01-01
              • 2010-10-09
              • 2021-01-30
              • 2011-04-21
              • 1970-01-01
              • 2021-11-06
              • 2023-03-21
              • 1970-01-01
              相关资源
              最近更新 更多