Ubuntu上的Haskell(GHC)中的ThreadDelay问题

时间:2011-09-15 18:38:22

标签: linux haskell ubuntu

我注意到我的一些机器上的GHC.Conc中的threadDelay函数有奇怪的行为。以下程序:

main = do print "start"
          threadDelay (1000 * 1000)
          print "done"
按预期,

需要1秒才能运行。另一方面,这个程序:

{-# LANGUAGE BangPatterns #-}
import Control.Concurrent

main = do print "start"
          loop 1000
          print "done"
  where loop :: Int -> IO ()
        loop !n =
          if n == 0 
          then return ()
          else do threadDelay 1000
                  loop (n-1)

在我的两台机器上运行大约需要10秒钟,但在其他机器上需要大约1秒钟,正如预期的那样。 (我使用'-threaded'标志编译了上述两个程序。)以下是Threadscope的屏幕截图,显示每10毫秒只有一次活动:Screenshot of ThreadScope showing that threadDelay of 1 millisecond sleeps for 10 milliseconds.

另一方面,这是来自我的一台机器的ThreadScope的截图,程序总共需要1秒钟:Screenshot of ThreadScope showing that threadDelay of 1 millisecond sleeps for about 1 milliseconds.

类似的C程序:

#include <unistd.h>
#include <stdio.h>

int main() {
  int i; 
  for (i=1; i < 1000; i++) {
    printf("%i\n",i);
    usleep(1000);
  }
  return 0;
}

做正确的事情,即运行'time ./a.out'给出输出,如:

1
2
...
999

real 0m1.080s
user 0m0.000s
sys  0m0.020s

之前有没有人遇到过这个问题,如果是的话,怎么解决这个问题呢?我在我的所有机器上运行ghc 7.2.1 for Linux(x86_64)并运行各种版本的Ubuntu。它在Ubuntu 10.04.2上工作得很糟糕,但在11.04很好。

3 个答案:

答案 0 :(得分:3)

threadDelay不是一个准确的计时器。它保证你的线程至少会在 中休眠,只要它的参数说它应该,但它不会承诺更多。如果您想要定期发生某些事情,您将不得不使用其他东西。 (我不确定是什么,但Unix' realtime alarm signal可能适合你。)

答案 1 :(得分:1)

我怀疑你忘了使用'-threaded'选项进行编译。 (我为6.12.3做了一次,并且一直有30毫秒的线程延迟。)

答案 2 :(得分:1)

如上所述,threadDelay只提供一个保证,只要你请求,你就会等待至少。 Haskell的运行时没有从OS获得特殊的合作

除此之外,它是操作系统的最佳努力。

可能值得对threadDelays的结果进行基准测试。例如:

module Main where
import Control.Concurrent
import Data.Time

time op = 
    getCurrentTime >>= \ t0 -> 
    op >> 
    getCurrentTime >>= \ tf -> 
    return $! (diffUTCTime tf t0)

main :: IO ()
main = 
    let action tm = time (threadDelay tm) >>= putStrLn . show in
    mapM action [2000,5000,10000,20000,30000,40000,50000] >>
    return ()

在我的窗户框上,这给了我:

0.0156098s
0.0156098s
0.0156098s
0.0312196s
0.0312196s
0.0468294s
0.0624392s

这表明延迟和getCurrentTime的组合具有15.6毫秒的分辨率。当我循环1000次延迟1000时,我最后等待15.6秒,所以这只是一个线程的最小等待。

在我的Ubuntu盒子(11.04,内核2.6.38-11)上,我获得了更高的精度(~100us)。

您可以通过保持程序更加繁忙来避免计时问题,因此我们不会将上下文切换掉。无论哪种方式,我建议你不要使用threadDelay进行计时,或至少检查时间并执行任何操作直到给定的时刻。

如果您愿意使用FFI,那么通过C进行的高精度睡眠可能对您有用,但成本是您需要使用绑定线程(至少为您的计时器)。