3

我在 Raspberry Pi 上用 Haskell 编写了一个测试程序,它在连接到 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 编译时添加 -thread 选项使声音更加清晰。

ghc -threaded buzzer1t.hs

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

谷歌搜索似乎表明 usleep 和朋友是操作系统级别的延迟,而 threadDelay 仅与 Haskell 程序本身中的线程有关。threadDelay 似乎也是更推荐的一种,并且被认为是更好的做法,尽管在这种情况下 usleep 显然更好。

4

1 回答 1

3

我认为文档是一个好的开始:

GHC 注意:threadDelay 是更好的选择。如果没有 -threaded 选项,usleep 将阻塞所有其他用户线程。即使使用 -thread 选项,usleep 本身也需要一个完整的操作系统线程。threadDelay 没有这些缺点。

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

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

当您使用线程运行时,您会获得多个操作系统线程来执行用户线程,但抓住一个而不释放它仍然有些敌意。除其他外,它会阻止垃圾收集器运行(它会一直等到它可以在已知安全点暂停所有用户线程,因此它不会破坏同时使用的内存),并且操作系统线程比用户线程更占用内存如果您添加额外内容以弥补丢失的并发性。

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

于 2020-06-02T02:45:22.637 回答