为什么睡眠无效?

17
为什么在以下代码中会立即返回?
{-# LANGUAGE ForeignFunctionInterface #-}
import Foreign.C.Types
import Data.Time.Clock
import Control.Concurrent

foreign import ccall unsafe "unistd.h sleep" 
    c_sleep :: CUInt -> IO CUInt

main :: IO ()
main = do
    getCurrentTime >>= print . utctDayTime
    c_sleep 10     >>= print                -- this doesn't sleep
    getCurrentTime >>= print . utctDayTime
    threadDelay $ 10 * 1000 * 1000          -- this does sleep
    getCurrentTime >>= print . utctDayTime
$ ghc --make Sleep.hs && ./Sleep
[1 of 1] Compiling Main             ( Sleep.hs, Sleep.o )
Linking Sleep ...
29448.191603秒
10
29448.20158秒
29458.211402秒

$ ghc --version
The Glorious Glasgow Haskell Compilation System, version 7.8.3

$ cabal --version
cabal-install version 1.20.0.3
using version 1.20.0.0 of the Cabal library 

注意:实际上,我想在C代码中使用 sleep函数来模拟一些耗时的计算,并在Haskell中调用函数,但这也不起作用,可能是因为相同的原因。


1
可能 GHC 运行时正在使用干扰 sleep 的信号。您是否检查了错误代码?也许您应该将其包装在循环中,以便在被中断时重新启动它(在这种情况下,最好使用 nanosleep 来获得更高的精度)。 - Rufflewind
1
@Rufflewind:sleep 不会返回错误代码,但会返回未休眠的秒数。尚未尝试 nanosleep,但 usleep 也无效。 - Zeta
错误代码通过 errno 变量“返回”。将 (c_sleep 10) 包装在 throwErrnoIf (/= 0) "sleep" 中,你会发现它被中断了。 - Rufflewind
请参阅此错误。虽然与此问题不完全相关,但它确实说明了 RTS,即使是单线程的 RTS,也会定期发送信号,这将中断任何 sleep 调用。 - Rufflewind
@Rufflewind:谢谢。天哪,为什么man 3 sleep没有提到errno的更新?我想我得写一个void nanosleep_loop(uint32_t),因为nanosleep也会受影响。票据可能已经回答了这个问题,所以请随意添加一个。 - Zeta
实际上,你是对的,sleep 的文档根本没有提到 errno(我看的是 nanosleep)。不过,文档确实说如果返回值为非零,则是由于被信号中断。 - Rufflewind
2个回答

12

GHC的运行时系统似乎使用信号来实现自己的 目的, 这意味着很快一个睡眠就会被其中一个信号打断。我认为这不是一个错误,可以说运行时系统自带其特性。Haskell方法是使用threadDelay,但对于C程序来说,要想访问它并不容易,需要一些技巧。

正确的方法是尽管受到其他信号的干扰,但反复恢复睡眠。我建议使用nanosleep,因为sleep只有秒级的精度,而信号似乎比这更频繁发生。

#include <errno.h>
#include <time.h>

/* same as 'sleep' except it doesn't get interrupted by signals */
int keep_sleeping(unsigned long sec) {
    struct timespec rem, req = { (time_t) sec, 0 }; /* warning: may overflow */
    while ((rem.tv_sec || rem.tv_nsec) && nanosleep(&req, &rem)) {
        if (errno != EINTR) /* this check is probably unnecessary */
            return -1;
        req = rem;
    }
    return 0;
}

1

所有的并发原语都有一个回收语句,它们可以阻塞的时间少于指定的时间 - 它们可能会虚假返回。这与语言无关,这是并发的特性,因此如果您想要等待恰好指定的时间,在任何语言中,您需要构造一个循环,在休眠后检查时钟。


虽然这是真的,但人们不会期望一个在单线程C领域中通常运行良好的有效的(u)sleep()调用在调用后立即被中断。尤其是如果没有使用-threaded和/或并发。 - Zeta
@Zeta 那么期望是错误的。没有人会为虚假唤醒指定原因 - 它是 虚假 的;它并没有说 "可能只会因为应用程序自身中断而唤醒" 。我认为编写直观的代码反对未指定的行为是没有意义的 - 就像我们不假设C语言中未初始化的 int 是零一样。 - Sassa NF

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