3

次の Haskell プログラムを考えてみましょう (私は主に学習目的でこれを行っています)。

import qualified Control.Concurrent.MSem as Sem
import System.Environment (getArgs)
import Control.Concurrent (forkIO)
import Control.Monad

-- Traverse with maximum n threads
parallelTraverse :: Foldable a => Int -> (b -> IO()) -> a b -> IO ()
parallelTraverse n action values = do
  sem <- Sem.new n
  forM_ values $ \value -> Sem.with sem (forkIO $ action value)

main :: IO ()
main = do
  args <- getArgs
  let nThreads = read . head $ args :: Int
  parallelTraverse nThreads print [(1::Int)..]

実行すると、メモリはすぐに数 GB に増加します。中間計算 (出力アクション) の結果を確実に破棄するために、さまざまな組み合わせを試しました。なぜまだスペースが漏れているのですか?

4

1 に答える 1

6

まず、次の部分に明らかな誤りがあります。

Sem.with sem (forkIO $ action value)

そこでのアクションではなく、「フォーク」操作の周りのマスタースレッドからセマフォに対処しています。以下は、それを実装する適切な方法です。

forkIO (Sem.with sem (action value))

つまり、フォークされたスレッドのコンテキストからセマフォをアドレス指定します。

次に、次のコードではparallelTraverse、無限リストで操作を呼び出しています。

parallelTraverse nThreads print [(1::Int)..]

その結果、スレッドが無限に分岐します。またforkIO、呼び出し元のスレッドの操作はほぼ瞬時に行われるため、すぐにリソースが不足しても驚くことではありません。


セマフォを使用してワーカースレッドの数を制限するには、withパターンはあなたの場合にはうまくいきません。代わりに、waitandの明示的な組み合わせを使用しsignalて、例外を適切に処理することを忘れないでください (予期される場合に備えて)。例えば、:

parallelTraverse :: Foldable a => Int -> (b -> IO()) -> a b -> IO ()
parallelTraverse n action values = do
  sem <- Sem.new n
  forM_ values $ \value -> do
    Sem.wait sem
    forkIO $ finally (action value) (Sem.signal sem)
于 2015-09-07T20:12:18.713 に答える