Strict evaluation techniques for concurrent channels in Haskell
我在玩弄haskell线程,我遇到了一个问题,就是如何通过一个通道来传递延迟评估的值。例如,使用n个工作线程和1个输出线程,工作线程将通信未计算的工作,而输出线程最终将为它们完成工作。
我已经在各种文档中阅读过这个问题,并看到了各种解决方案,但是我只找到了一个有效的解决方案,而其余的则没有。下面是一些代码,其中工作线程启动一些计算可能需要很长时间。我按降序启动线程,这样第一个线程将占用最长的时间,而后面的线程将提前完成。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 | import Control.Concurrent (forkIO) import Control.Concurrent.Chan -- .Strict import Control.Concurrent.MVar import Control.Exception (finally, evaluate) import Control.Monad (forM_) import Control.Parallel.Strategies (using, rdeepseq) main = (>>=) newChan $ (>>=) (newMVar []) . run run :: Chan (Maybe String) -> MVar [MVar ()] -> IO () run logCh statVars = do logV <- spawn1 readWriteLoop say"START" forM_ [18,17..10] $ spawn . busyWork await writeChan logCh Nothing -- poison the logger takeMVar logV putStrLn"DONE" where say mesg = force mesg >>= writeChan logCh . Just force s = mapM evaluate s -- works -- force s = return $ s `using` rdeepseq -- no difference -- force s = return s -- no-op; try this with strict channel busyWork = say . show . sum . filter odd . enumFromTo 2 . embiggen embiggen i = i*i*i*i*i readWriteLoop = readChan logCh >>= writeReadLoop writeReadLoop Nothing = return () writeReadLoop (Just mesg) = putStrLn mesg >> readWriteLoop spawn1 action = do v <- newEmptyMVar forkIO $ action `finally` putMVar v () return v spawn action = do v <- spawn1 action modifyMVar statVars $ \vs -> return (v:vs, ()) await = do vs <- modifyMVar statVars $ \vs -> return ([], vs) mapM_ takeMVar vs |
使用大多数技术,结果按生成的顺序报告;即,首先是最长运行的计算。我将其解释为输出线程正在执行所有工作:
1 2 3 4 5 6 7 8 9 10 11 12 | -- results in order spawned (longest-running first = broken) START 892616806655 503999185040 274877906943 144162977343 72313663743 34464808608 15479341055 6484436675 2499999999 DONE |
号
我想答案应该是严格的渠道,但他们不起作用。我理解字符串的whnf是不够的,因为这只会强制使用最外面的构造函数(字符串的第一个字符为nil或cons)。
1 2 3 4 5 6 7 8 9 10 11 12 | -- results in order finished (shortest-running first = correct) START 2499999999 6484436675 15479341055 34464808608 72313663743 144162977343 274877906943 503999185040 892616806655 DONE |
那么,为什么不通过严格的渠道或
这里有两个问题。
第一次尝试(使用显式
结果是,此代码:
1 | force s = evaluate $ s `using` rdeepseq |
号
将起作用(与中一样,具有与
使用严格通道的情况有点棘手,但我相信这是由于严格并发中的一个bug造成的。昂贵的计算实际上是在工作线程上运行的,但它对您没有太大帮助(您可以通过在字符串中隐藏一些异步异常并查看异常出现在哪个线程上来明确检查这一点)。
虫子是什么?让我们来看一下严格的
1 2 3 4 5 6 |
我们看到在评估thunk之前,
对于
我将发送有关此问题的Don mail,看看他是否同意此行为属于次优行为。
唐同意这种行为是次优的。我们正在开发一个补丁。