为什么我的并行遍历Haskell程序泄漏内存



考虑下面的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。我尝试了各种组合,以确保丢弃中间计算的结果(打印操作)。为什么它还在漏空间?

首先,下面这段有一个明显的错误:

Sem.with sem (forkIO $ action value)

你正在寻址来自主线程的"fork"操作周围的信号量,而不是那里的操作。下面是实现它的正确方法:

forkIO (Sem.with sem (action value))

。,从分叉线程的上下文中寻址信号量。

其次,在下面的代码中,您将在无限列表上调用parallelTraverse操作:
parallelTraverse nThreads print [(1::Int)..]

导致线程无限分叉。由于forkIO操作对于调用线程来说大致是瞬时的,所以很快就会耗尽资源,这一点也不奇怪。


要使用信号量来限制工作线程的数量,with模式根本无法在您的情况下使用。相反,您应该使用waitsignal的显式组合,并且不要忘记正确地处理异常(以防您期望出现异常)。如:

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)

最新更新