睡觉()邪恶吗?

时间:2009-07-08 08:25:47

标签: language-agnostic

首先,很多情况下Sleep() 误用,例如“同步”线程或定期轮询通知函数会执行的值(在Win32中{ {1}}例如)

但是其他用例呢? WaitForSingleObject总是邪恶吗?如果不是,Sleep有什么好的用例?如果是,为什么几乎所有语言都有某种Sleep语句?

PS:由于another问题的一条评论,我问过这个问题。 OP表示,他认为Sleep应该像Sleep一样避免使用。{/ p>

18 个答案:

答案 0 :(得分:40)

我实际上相信你所关联的断言是正确的。问题是正在使用睡眠(如您所述)作为通知机制的低效替代品。睡眠总是不如正确实施的通知机制,如果您正在等待活动。如果您确实需要等待特定时间,那么睡眠是合适的。

例如,在某些网络协议中,您使用称为指数退避的技术,如果您在网络上检测到冲突,则需要在重试之前等待一段随机(并且指数增加)的时间。在这种情况下,睡眠是合适的,因为你没有试图等待事件发生,你正试图等待一段特定的时间来通过。

答案 1 :(得分:20)

我发现在Web应用程序中编写回调时使用Thread.Sleep(1000);来模拟延迟很有用。

使用Microsoft的AJAX组件的一个简单示例是在更新面板中放置一个按钮...在onclick处理程序上使用Thread.Sleep(),然后更新进度面板将显示该时间量。

上线时显然删除Thread.Sleep()... :)

答案 2 :(得分:19)

在现实世界中,使用正确的同步原语并不总是可行的:

  • 有时您需要轮询某些内容,直到满足条件为止,并且您使用sleep来避免浸泡机器。

  • 有时您会主动希望进入睡眠状态 - 例如,当您希望线程占用最少的机器资源并且您使用的是操作系统时( cough Windows cough )在防止低优先级线程浸泡机器方面做得不好。

编辑以回应Kieveli的评论:我认为你应该尽可能地实施适当的信令。因此,工作线程应该对队列执行阻塞读取而不是休眠和轮询。我说有时你被迫与那些排除做正确的事情的系统进行交互。我并不是说你不应该在合理可能的情况下做正确的事情 - “半秒钟就足够了”,也许,但是接近瞬时会明显更好!

答案 3 :(得分:8)

与所有其他“是邪恶的”问题一样的答案。

这是一个低级操作。人们有时会使用低级操作来重新发明已经作为高级操作使用的轮子。这是不明智的,你应该确保它不是你正在做的。但是如果你实际上是在低级编程,那么你可能会使用低级操作。一些高级程序员会认为你是邪恶的或愚蠢的或两者兼而有之。你知道的更好,因为你是那些编写高级平台的人,他们可以自己闭嘴或自己做。

考虑到使用高级构造的邪恶扭曲与使用低级构造的邪恶扭曲之间的选择,有些人认为你应该总是更喜欢前者。我并没有真正得到他们的POV,但他们至少是一致的,所以你不能责怪他们,除非后者明显更快/更小/更好。

良好的睡眠用途:

  • 当你必须阻止到某个时间(或一段时间)。您的语言/库/操作系统可能提供也可能不提供阻止计时器来完成工作。如果没有,那么睡眠是永远可用的选择。如果是这样的话,它可能会完全与沼泽标准的睡眠循环完全相同,所以请使用少量代码。

  • 实现其他原语时,例如计时器。

  • 当您在POSIX或其他任何地方使用信号进行异步线程间或进程间消息传递时。你只是一直睡觉,信号处理人员要么工作,要么设置状态告诉你需要做什么工作,你醒来时就做。当然你可以使用其他API,但除非你主动偏爱那个API,否则你只是避免睡觉,因为互联网上的一些家伙告诉你。也许当他编写一种没有睡眠的编程语言,并且比你当前的语言更好时,你就会转而使用它。

睡眠的主要问题(除了它之间并没有真正做多次线程之间的交互,因此通常是需要这些工作的错误工具)就是即使你没有消耗任何CPU时间,你也是仍然消耗资源,因为你有一个线程无所事事。根据平台,线程可能很昂贵。在单核系统上,两个的第二个线程特别昂贵,因为如果你可以摆脱它,你的大多数数据同步问题就会消失。因此,如果您可以合理地设计您的应用程序,使其永远不需要多个线程,并且永远不会阻塞,除非在某些事件循环中,因为一切都是使用异步消息完成的,所以这样做,您将不需要睡眠。

答案 4 :(得分:7)

tail -f的实施需要sleep()。即stat()文件,如果已更改,请读入差异,然后sleep()稍微继续......继续直到中断。

我认为这是sleep()

的有效用法

作为Linux的一个例子,strace tail -f foo,安顿下来

clock_gettime(CLOCK_REALTIME, {1247041913, 292329000}) = 0
nanosleep({1, 0}, NULL)                 = 0
fstat64(3, {st_mode=S_IFREG|0755, st_size=357, ...}) = 0
clock_gettime(CLOCK_REALTIME, {1247041914, 311933000}) = 0
nanosleep({1, 0}, NULL)                 = 0
fstat64(3, {st_mode=S_IFREG|0755, st_size=357, ...}) = 0
clock_gettime(CLOCK_REALTIME, {1247041915, 355364000}) = 0
....

确定这是nanosleep()但适用相同的规则。

Solaris truss tail -f foo安顿下来

read(0, 0x00024718, 65537)                      = 0
nanosleep(0xFFBFF1A0, 0xFFBFF198)               = 0
read(0, 0x00024718, 65537)                      = 0
nanosleep(0xFFBFF1A0, 0xFFBFF198) (sleeping...)
nanosleep(0xFFBFF1A0, 0xFFBFF198)               = 0
read(0, 0x00024718, 65537)                      = 0

答案 5 :(得分:5)

我在这里看不到它,但是sleep()是一个非常肯定的方法,可以让你的CPU时间片更有价值。当你睡眠()时,操作系统会控制并决定下一个应该运行的人,这也允许来自同一进程的其他线程有CPU时间。当生成多个线程并且Controller等待Workers准备就绪时,这可以减轻单CPU机器上的CPU压力。

答案 6 :(得分:5)

除了Chalkey提到的测试目的之外,我发现我从未真正需要在Linux,Windows和OS / X等高级操作系统上调用 sleep 。即使在预期程序只是等待一段时间的情况下,我也会在信号量超时时使用 wait 函数,以便我可以通过释放信号量立即结束线程/有人要求我的程序终止:

# [pseudo-code]

if wait_semaphore(exit_signal_semaphore, time_to_sleep)
  # another thread has requested this one to terminate
  end_this_thread
else
  # event timed-out; wait_semaphore behaved like a sleep function
  do_some_task
end

答案 7 :(得分:4)

如果您正在使用Sleep()处理任何不仅涉及您正在操作的线程的情况,那么您可能会做错事......

也就是说,有时你想暂停处理,例如在无休止(或有效的)工作循环中,给其他进程一些喘息的空间。

答案 8 :(得分:2)

恕我直言,如果你有一个编写良好的操作系统并且你在操作系统的正常范围内工作,你只能避免睡眠()。

当你必须在操作系统之外工作(例如嵌入式软件/固件)和普通领域之外(例如驱动程序)时,你就无法访问线程技术,因为没有任何东西可以为你进行任务切换。因此,你别无选择,只能做自旋锁和轮询等待,因为没有先发制人的帮助。

此外,如果您正在针对基于非事件的API进行集成,那么您有时别无选择,只能轮询信息,因此在这种情况下,您最好的选择是至少睡觉而不是自旋锁以让操作系统在您执行操作时执行某些操作等待。

答案 9 :(得分:2)

如果用于“忙碌等待”,则睡眠被滥用。但是在某些情况下,使用睡眠 在自旋锁中或当您没有允许您使用不同事件源的API时。

答案 10 :(得分:2)

几乎总能避免sleep()来电。下面给出了一个带有事件循环和多个线程的程序中的一些场景,因为这是最常见的场景sleep()是(ab)使用的。

  1. 作为监视/轮询功能(即'wait-every-every-100-ms-before-repeated-this-function') - 超时机制可以取代睡眠。超时本质上是请求应用程序的主事件处理程序在一定时间后调用函数。此“提醒”存储在内部队列中,并在计时器到期后执行。例如:

    class DownloadMonitor 
    {
      int UpdateProgressBar();
      void Monitor()
      {
        if (UpdateProgressBar() < 100) 
        { 
          Application::SetTimeOut("100 ms", this, UpdateProgressBar); 
        }
      }
    }
    
    这适用于像“tail -f”这样的例子。良好实施的超时(例如,检查文件的修改时间)将允许用户在民意调查之间控制程序。超时的一个缺点是它仅限于带有事件循环的线程(这通常是主线程)。

  2. 作为线程同步机制 - 这是非常危险的,因为它估计CPU处理某些事情所花费的时间。替换为二进制信号量。

  3. 信号量和超时的简单示例:

    • 视频转换软件通常具有用于图像分析,磁盘IO和UI的单独线程(允许诸如“在后台运行”,“取消”,“暂停”等命令)。

    • 图像分析需要在磁盘IO被要求将文件的转换部分写入磁盘之前完成,并且需要在UI更新之前完成。

    • 允许两个线程在循环中运行。 DiskIO线程通过等待“完成转换”信号量开始。当转换后的图像在队列中时,图像分析可以发布此信号量。磁盘IO线程将此映像写入磁盘,更新“Percent Done”之类的变量,然后再次等待信号量。 UI线程使用超时机制定期读取“Percent Done”变量。各种UI状态由UI设置,并在每个循环中至少由转换和磁盘IO线程检查一次。

    睡眠的唯一有效用途可能是模拟活动。这最适合用于测试和遗漏客户构建。

答案 11 :(得分:1)

我制作了一个用于读取Flash芯片的设备,我通过计算机的并行端口进行控制。在发送另一个时钟信号之前,我需要等待数据传播通过芯片。所以我使用了usleep()

答案 12 :(得分:1)

Windows:通常如果我需要“睡眠一个线程”我不会使用sleep(),而是使用MsgWaitForMultipleObjects或类似的,它允许在等待通知时指定超时。这样线程仍然可以对任何事件做出反应(例如WM_QUIT),而进程不必等待线程退出睡眠,检查事件然后退出。

答案 13 :(得分:1)

在Java&amp; .Net,sleep()是可中断的。

因此,如果你sleep(),并且另一个线程在需要时中断你(导致抛出InterruptedException),那就没有错。

我正在编写一个外部队列的轮询机制 - 我的代码基本上是:

while (true)
{
  items = takeFromQueue();
  if (items.size() == 0)
    sleep(1000);
}

另一个线程可以通过调用thread.interrupt();

安全地停止这个线程

答案 14 :(得分:1)

有时,您不希望不断刷新显示器,例如在系统或网络监视器中。另一个例子是观察(1) - 如何在没有睡眠的情况下实现它?

答案 15 :(得分:0)

  

但其他用途呢?睡觉了   永远邪恶?如果没有,有什么用处   睡眠病例?

你需要每隔10分钟做一些事情,我认为让你的程序/线程进入睡眠状态没有任何问题,直到再次工作为止。

答案 16 :(得分:0)

关于你如何使用它的全部内容。

在我们使用的一些代码中,一位开发人员睡了5(5)5毫秒,注意“睡眠,所以我的电脑仍然可用”,这很好,除了抱怨速度性能的客户。所以我删除了它,吞吐量增加了一倍,但是当睡眠(0)抱怨他的机器在进行大数据加载测试时没有响应时,他把它放回去了。 Groan ,获取新的开发PC

在哪里 - 因为我没有问题是你真的不想加载系统睡眠,或者将睡眠与轮询类型动作结合使用。

答案 17 :(得分:0)

我对Sleep的最常见用法是说明多线程代码中的问题,但除此之外,还有一些其他答案中提到的有效用法。