私はHaskellスレッドを使用していますが、チャネル全体でレイジー評価された値を伝達するという問題に取り組んでいます。たとえば、N個のワーカースレッドと1個の出力スレッドでは、ワーカーは未評価の作業を通信し、出力スレッドはそれらの作業を終了します。Haskellでの同時チャネルの厳密な評価手法
さまざまなドキュメントでこの問題について読んだことがありますが、さまざまな解決策がありましたが、私は1つの解決策しか見つけられず、残りの解決策は見つからなかった。以下は、ワーカースレッドが長い時間がかかる計算を開始するコードです。降順でスレッドを開始するので、最初のスレッドは最長で、後のスレッドは先に終了する必要があります。
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
ほとんどの手法を使用して、結果は生成された順序で報告されます。すなわち、最も長く実行されている計算が最初に行われます。私は、出力スレッドがすべての作業を行っていることを意味するためにこれを解釈:
-- results in order spawned (longest-running first = broken)
START
892616806655
503999185040
274877906943
144162977343
72313663743
34464808608
15479341055
6484436675
2499999999
DONE
私はこの答えは、厳しいチャンネル、だろうと思ったが、彼らは動作しませんでした。文字列のWHNFは、最外のコンストラクタ(文字列の最初の文字のnilまたはcons)を強制するだけで十分ではないことを理解しています。 rdeepseq
は完全に評価されるはずですが、違いはありません。私が見つけた唯一のことは、文字列内のすべての文字にControl.Exception.evaluate :: a -> IO a
をマップすることです。 (いくつかの異なる選択肢のコードでforce
機能のコメントを参照してください)ここでの結果はControl.Exception.evaluate
である:
-- results in order finished (shortest-running first = correct)
START
2499999999
6484436675
15479341055
34464808608
72313663743
144162977343
274877906943
503999185040
892616806655
DONE
なぜ厳格なチャネルまたはrdeepseq
は、この結果を生成しませんか?他のテクニックはありますか?なぜ私は最初の結果が壊れているのか誤解していますか?
2番目の部分は非常に興味深いです(まだ1番目を熟考しています)。だから、厳密なチャンネルはスレッドを送信すると評価されますが、最初にチャンネルの*予約*をしますか?作業者は望む順番で作業を終えることができますが、チャンネルはFCFSを(FFFS =最初の作業ではなく、最初の作業ではなく)シリアル化します。ありがとう。 – chrisleague
予約はお勧めです。 –
補足として、リストのすべての要素の評価を強制するdeepseqは必ずしも必要ではありません。 'length xs 'seq' xs'で行うことができるリストの背骨だけを評価するのに十分なことがよくあります – sclv