为什么我的并行遍历Haskell程序会泄漏内存?
Why does my parallel traversal Haskell program leak memory?
考虑以下 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。我尝试了各种组合以确保丢弃中间计算的结果(打印操作)。怎么还在漏space?
首先,你在下面这一段有一个明显的错误:
Sem.with sem (forkIO $ action value)
您正在围绕 "fork" 操作而不是那里的操作从主线程寻址信号量。以下是正确的实现方式:
forkIO (Sem.with sem (action value))
即,从分叉线程的上下文中寻址信号量。
其次,在以下代码中,您将对无限列表调用 parallelTraverse
操作:
parallelTraverse nThreads print [(1::Int)..]
这导致线程的无限分叉。由于 forkIO
操作对于调用线程来说大致是瞬时的,因此您 运行 很快就会耗尽资源也就不足为奇了。
要使用信号量来限制工作线程的数量,with
模式在您的情况下根本行不通。相反,您应该使用 wait
和 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)
考虑以下 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。我尝试了各种组合以确保丢弃中间计算的结果(打印操作)。怎么还在漏space?
首先,你在下面这一段有一个明显的错误:
Sem.with sem (forkIO $ action value)
您正在围绕 "fork" 操作而不是那里的操作从主线程寻址信号量。以下是正确的实现方式:
forkIO (Sem.with sem (action value))
即,从分叉线程的上下文中寻址信号量。
其次,在以下代码中,您将对无限列表调用 parallelTraverse
操作:
parallelTraverse nThreads print [(1::Int)..]
这导致线程的无限分叉。由于 forkIO
操作对于调用线程来说大致是瞬时的,因此您 运行 很快就会耗尽资源也就不足为奇了。
要使用信号量来限制工作线程的数量,with
模式在您的情况下根本行不通。相反,您应该使用 wait
和 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)