是睡眠()邪恶?
首先,有很多情况下, Sleep()
被滥用 ,例如“同步”线程或定期轮询通知函数将执行的值(例如,在Win32的WaitForSingleObject
中)
但是其他用例呢? Sleep
总是邪恶吗? 如果不是,那么Sleep
好用例是什么? 如果是,为什么几乎所有的语言都有某种Sleep
声明?
PS:我之所以问这个问题是因为另一个问题的意见之一。 那里的OP说,在他看来, Sleep
是要避免像goto
。
我其实相信你所说的断言是正确的。 问题是睡眠正在被使用(如你所注意的),作为通知机制的低效替代。 睡眠总是不如正确实施的通知机制, 如果你正在等待一个事件 。 如果你真的需要等待一些特定的时间 ,那么睡觉是适当的。
例如,在某些networking协议中,您可以使用称为指数退避的技术,如果您在networking上检测到冲突,则您需要等待随机(并呈指数增长)的时间,然后再重试。 在这种情况下,睡眠是合适的,因为你不是在等待事件发生,而是在等待一段特定的时间。
我发现使用Thread.Sleep(1000);
很好用Thread.Sleep(1000);
在Web应用程序中编写callback时模拟延迟。
微软的AJAX组件的一个简单的例子是在更新面板中放置一个button… onclick处理程序使用Thread.Sleep(),更新进度面板将显示该时间量。
注意删除Thread.Sleep()上线时:)
在现实世界中,使用正确的同步原语并不总是实用的:
-
有时你需要轮询一些东西,直到满足条件,然后在那里使用
sleep
来避免浸泡机器。 -
有时候你主动想睡觉 – 比如说,当你想要一个线程占用最less的机器资源,而且你正在使用一个操作系统( 咳嗽 Windows 咳嗽 )时,它并不能很好地防止低优先级线程的浸泡机器。
编辑回应Kieveli的评论:我认为你应该实施适当的信号,当你可以。 所以一个工作线程应该在队列上进行阻塞读取而不是睡眠和轮询。 我在说,有时候你会被迫与那些阻碍做正确事情的系统交互。 我并不是说在合理可能的情况下不应该做出正确的事情 – “半秒钟内就足够好”,也许,但是几乎是瞬间的会明显更好!
与其他所有问题一样的“是邪恶的”问题。
这是一个低级别的操作。 人们有时会使用低级操作来重塑已经作为高级操作可用的车轮。 这是不明智的,你应该确保这不是你在做什么。 但是如果你实际上是在低级别编程,那么你可能会使用低级别的操作。 一些高级程序员会认为你是邪恶或愚蠢的,或两者兼而有之。 你知道的更好,既然你是写高层平台的人,他们可以闭嘴,也可以自己做。
在一个使用高级构造的邪恶的扭曲和使用低级构造的邪恶的扭曲之间作出select的时候,有些人认为你应该总是喜欢前者。 我真的没有得到他们的POV,但他们至less是一致的,所以你不能责怪他们,除非后者明显更快/更小/更好。
良好的睡眠用途:
-
当你不得不阻塞一段时间(或某段时间)。 你的语言/图书馆/操作系统可能提供或不提供阻塞计时器来完成这项工作。 如果不是,那么睡眠是永远可用的select。 如果是这样的话,它可能会和bog标准的睡眠循环完全一样,所以不pipe代码是多less。
-
在实现定时器等其他原语时。
-
当你在POSIX上使用信号进行asynchronous线程间或进程间消息传递时, 你一直睡觉,指示处理者要么做的工作,要么设置状态,告诉你需要做什么工作,而且当你醒来的时候做。 当然,你也可以使用其他的API,但是除非你主动的喜欢这个API,否则你只是在避免睡眠,因为互联网上的一些老兄告诉你。 也许当他编写一种没有睡眠的编程语言,并且比当前的语言更好的时候,那么你就可以切换到这种语言。
睡眠的主要问题(除了线程之间没有太多的交互,所以通常是需要这个工作的错误工具)是即使你没有消耗任何CPU时间,你仍然在消耗资源因为你有一个线程坐在什么都不做。 根据平台,线程可能是昂贵的。 在单核系统上,两个线程中的第二个线程特别昂贵,因为如果你能摆脱它,大部分的数据同步问题就会消失。 所以,如果你可以合理地devise你的应用程序,使它永远不需要多个线程,除非在某些事件循环中,永远不会阻塞,因为一切都是用asynchronous消息完成的,那么这样做,你就不需要睡眠。
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
除了像Chalkey提到的testing目的之外,我发现我从来不需要在Linux,Windows和OS / X等高级操作系统上调用睡眠 。 即使在程序期望等待一段时间的情况下,我也会在超时的信号量上使用一个等待函数,这样,如果有人/某人要求我的程序终止,我可以通过释放信号立即结束线程:
# [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
我看不到它在这里提到,但睡眠()是一个非常可靠的方法,让您的CPU时间片更有价值的过程。 当你sleep()时,OS接受控制并决定下一个谁应该运行,这也允许来自同一进程的其他线程有CPU时间。 当多个线程产生时,这会减轻单CPU机器上的CPU压力,并且Controller等待Worker准备就绪。
如果您正在使用Sleep()处理涉及的不仅仅是您正在操作的线程的任何情况,那么您很可能会做出错误的事情…
也就是说,有些时候你想暂停处理,例如在一个无休止的(或有效的)工作循环中,给其他进程一些喘息的空间。
恕我直言,你只能避免睡眠(),当你有一个写得很好的操作系统,你正在操作系统的正常领域。
当你不得不在一个操作系统(例如,embedded式软件/固件)之外以及在正常的领域(例如驱动程序)之外工作时,那么你就无法使用线程技术,因为没有任何东西可以为你做任务切换。 因此,你别无select,只能像螺旋锁和轮询等待,因为没有任何帮助你的预兆。
另外,如果您正在整合非基于事件的API,那么您有时别无select,只能轮询信息,所以在这种情况下,您最好的select是至less睡眠,而不是自旋锁,让操作系统在等待时执行某些操作。
睡眠被滥用,如果它被用于“忙等待”。 但是,在某些情况下,睡眠用于旋转locking,或者当您没有可以使用不同事件源的API时。
sleep()
调用几乎总是可以避免的。 下面给出了一个带有事件循环和multithreading的程序中的一些场景,因为这将是sleep()
被使用的最常见的场景。
-
作为监控/轮询function(即“等待每100毫秒重复此function”),超时机制可以取代睡眠。 超时本质上要求应用程序的主要事件处理程序在一段时间后调用一个函数。 这个“提醒”存储在一个内部队列中,并在定时器到期后执行。 例如:
class DownloadMonitor { int UpdateProgressBar(); void Monitor() { if (UpdateProgressBar() < 100) { Application::SetTimeOut("100 ms", this, UpdateProgressBar); } } }
这也适用于例如“tail -f”的例子。 一个很好的实现超时(例如检查文件的修改时间)将允许用户在轮询之间控制程序。 超时的一个缺点是它只限于带有事件循环的线程(这通常是主线程)。
-
作为线程同步机制 – 这是非常危险的,因为它估计CPU处理某些东西所花费的时间。 replace为二进制信号量。
信号量和超时的一个简单例子:
-
一个video转换软件通常有单独的线程进行图像分析,磁盘IO和UI(允许像“在后台运行”,“取消”,“暂停”等命令)。
-
在磁盘IO被要求将文件的转换部分写入磁盘之前,图像分析需要完成,并且需要在UI自动更新之前完成。
-
允许两个线程在循环中运行。 DiskIO线程以“完成转换”信号等待开始。 转换后的图像在队列中时,图像分析可以发布此信号量。 磁盘IO线程将此映像写入磁盘,更新“百分比完成”等variables,然后再次等待信号量。 UI线程使用超时机制定期读取“完成百分比”variables。 各种UI状态由UI设置,并且在每个循环中由转换和磁盘IO线程检查至less一次。
可能唯一有效的睡眠使用是模拟活动。 这最适合用于testing,而不用客户构build。
有时候,你不想不断刷新你的显示,例如在系统或networking监视器。 另一个例子是手表(1) – 你将如何执行,没有睡眠?
在Java和.Net中,sleep()是可以中断的。
所以,如果你sleep(),并且另一个线程在需要的时候中断你(导致一个InterruptedException被抛出),那没有什么错。
我目前正在写一个轮询机制通过一些外部队列 – 我的代码基本上是:
while (true) { items = takeFromQueue(); if (items.size() == 0) sleep(1000); }
而另一个线程可以通过调用thread.interrupt()来安全地停止这个线程。
Windows:通常,如果我需要“睡一个线程”,我不会使用sleep(),而是使用MsgWaitForMultipleObjects或类似的,它允许指定一个等待通知超时。 这样,线程仍然可以对任何事件作出反应(例如WM_QUIT),而不必等待线程退出睡眠状态,然后检查事件并退出。
我制作了一个阅读Flash芯片的设备,通过我的电脑的并行端口进行控制。 在发送另一个时钟信号之前,我需要等待数据通过芯片传播。 所以我用了usleep()
。
我最常用的Sleep
就是说明multithreading代码中的问题,但除此之外还有一些其他答案中提到的有效用法。
这全是关于你如何使用它。
在一些我们在工作中使用的代码中,一个开发者把睡眠时间设置为5毫秒,并且评论“睡眠,所以我的电脑仍然可以使用”,这很好,除了我们的客户抱怨速度performance。 所以我把它移走了,吞吐量增加了一倍,但是当他做了大数据加载testing的时候,他却抱怨说他的机器没有响应。 呻吟 ,得到一个新的开发电脑
哪里 – 因为我没有问题是你真的不想加载系统睡觉,或使用睡眠与轮询types的行动。
但是其他用途呢? 睡眠总是邪恶吗? 如果不是,那么睡眠的好用例是什么?
如果你需要每隔10分钟做一些事情,只要把你的程序/线程hibernate,直到重新开始工作,我都没有看到任何问题。