首页 > 解决方案 > 除非我打印一些东西,否则并发(多线程)程序会挂起

问题描述

我有一个函数,我使用forkIO将其分叉到一个线程中。该函数在ReaderT monad 转换器中运行,以便我可以传入只读配置记录:

main :: IO ()
main = do 
    ...
    forkIO $ runReaderT watcher config

该函数使用tryTakeMVar 监视watcherMVar (我不希望它阻塞。) MVar 存储在配置中并被称为“抽屉”,因为它的行为类似于正在监视的线程之间的事务抽屉,基本上是跳过渠道。mainwatcher

printThing有一个签名printThing :: Thing -> ReaderT Config IO ()并调用putStrLn打印一个Thing.

watcher :: ReaderT Config IO ()                                                                                                                                                                       
 watcher = do
     cfg <- ask
     mNewThing <- liftIO $ tryTakeMVar $ drawer cfg
     case mNewThing of
         Nothing -> do
            --liftIO $ putStr ""  -- uncommenting this helps
            watcher
         Just newThing -> do
             printThing newThing
             watcher

问题是程序在运行时挂起。它似乎陷入了一个循环。调用putStr ""inmain并没有帮助,但是,调用putStr "" inside确实触发了线程——它开始旋转并按预期watcher打印出s 。Thing

我能想到的只是我被懒惰咬了,但我不确定在哪里。我已经尝试过$!尽可能使用。

我在某些条件下执行 IO 操作watcher,但不是全部。那是问题吗?我需要在所有条件分支中执行 IO 操作吗?

ReaderT如果有帮助,在我把所有东西都包在变压器里之前,我没有这个问题。我只是config作为一个论点四处走动。

标签: multithreadinghaskellconcurrency

解决方案


尽管您的问题中有文字,但我建议您watcher阻止。确实很少需要对 s 进行非阻塞操作MVar;通常想要它表明你还没有完全内化“fork everything”的心态。所以:

watcher :: ReaderT Config IO ()
watcher = do
    cfg <- ask
    newThing <- liftIO . takeMVar $ drawer cfg
    printThing newThing
    watcher

我们可以单独解决“如何实现效果 X,在我看来需要非阻塞操作,而只使用阻塞操作?”形式的问题?如果您将编写一个单独的问题,其中包含有关效果 X 的一些详细信息。

旁注:我很想用以下方式写上面的内容,这具有相同的含义,但在美学上更吸引我:

watcher = forever (asks drawer >>= liftIO . takeMVar >>= printThing)

推荐阅读