Haskell: 为什么 usleep + threaded 编译选项比 threadDelay 更精确?

Haskell: why is usleep + threaded compile option more precise than threadDelay?

我在 Haskell 上写了一个测试程序,在 Raspberry Pi 上演奏了一首悦耳的曲子 在连接到 GPIO 引脚的蜂鸣器上。

以下是我使用的导入:

import qualified Control.Concurrent as C
import qualified Control.Monad as M
import System.IO
import qualified System.Posix.Unistd as P

以下是通过写入来切换引脚的函数 /sys/class/gpio/gpio16/value 文件:

changePin2 :: Handle -> String -> Int -> IO ()
changePin2 handle onOff delay = do
  pos <- hGetPosn handle
  hPutStr handle (onOff ++ "\n")
  hFlush handle
  hSetPosn pos
  P.usleep delay
  --C.threadDelay delay

blinkOn2 :: Handle -> Int -> IO ()
blinkOn2 handle delay = do
  changePin2 handle "1" delay
  changePin2 handle "0" delay

最后,这是一个在下一个音符之前暂停播放一个音符的示例:

  mapM_ (blinkOn2 h) (replicate 26 1908)
  P.usleep 50000
  -- C.threadDelay 50000

当我第一次尝试时,我使用了threadDelay,听起来很糟糕。它的音调很低, 这表明延迟时间比预期的要长,并且所有音符听起来都差不多。 使用 usleep 函数大大改善了事情。 最后,在使用 ghc 编译时添加 -threaded 选项使声音更加清晰。

ghc -threaded buzzer1t.hs

我不明白为什么其中任何一个都会改进它,如果有人知道它会有很大帮助。

谷歌搜索似乎显示 usleep 和朋友在 OS 级别延迟,而 threadDelay 仅适用于 Haskell 程序本身的线程。 threadDelay 也似乎是 更推荐的一种并被认为是更好的做法,即使在这种情况下 usleep 显然是 优越.

我认为 the documentation 是一个好的开始:

GHC Note: threadDelay is a better choice. Without the -threaded option, usleep will block all other user threads. Even with the -threaded option, usleep requires a full OS thread to itself. threadDelay has neither of these shortcomings.

进一步扩展:GHC 运行时在系统线程上多路复用用户线程。默认运行时仅使用单个 OS 线程,无论有多少用户线程。大多数对外部代码的阻塞调用都是这样编写的,即它们在外部代码中取消当前 Haskell 用户线程的调度,允许与 Haskell 代码同时执行。这意味着即使是具有单个 OS 线程的默认运行时也可以处理同时执行 IO 的多个用户线程,例如。

在这个世界上,实际上阻塞OS线程被认为是有点敌对的activity。 threadDelay 只是将当前线程标记为不可运行,直到指定的时间量已过期。这对运行时系统更加友好,因为它释放了底层 OS 线程。

当你使用线程运行时,你会得到多个OS个线程来执行用户线程,但是抢一个不释放还是有点敌意。除其他事项外,它防止垃圾收集器 运行(它一直等到它可以在已知的安全点暂停所有用户线程,因此它不会破坏并发使用的内存),并且 OS 线程是如果你添加额外的东西来弥补丢失的并发性,那么内存比用户线程多得多。

所以对于大多数软件来说,threadDelay 是一个更好的公民。但它有缺点。线程不一定立即恢复。它可以在给定时间安排,但这并不意味着它实际运行。这仍然取决于其他线程的屈服。这几乎肯定是您遇到麻烦的原因 - 等待从可运行到实际 运行 的额外延迟。 usleep 专门用于遇到阻碍的情况。似乎是在需要时使用它的一个很好的理由。