防止"getCurrentDirectory: resource exhausted (Too many open files)"错误



我正在尝试对一大堆小文件运行Parsec解析器,并收到错误消息,说我打开的文件太多。我知道我需要使用严格的 IO,但我不确定该怎么做。这是有问题的代码:

files = getDirectoryContents historyFolder
hands :: IO [Either ParseError [Hand]]
hands = join $ sequence <$> parseFromFile (many hand) <<$>> files

注意:我的<<$>>函数是这样的:

(<<$>>) :: (Functor f1, Functor f2) => (a -> b) -> f1 (f2 a) -> f1 (f2 b)
a <<$>> b = (a <$>) <$> b
我不知道

你的parseFromFile函数现在是什么样子的(把它包含在问题中可能是个好主意),但我猜你正在使用Prelude.readFile,正如@Markus1189指出的那样,它包括惰性I/O。要获得严格的I/O,您只需要一个严格的readFile,例如Data.Text.IO.readFile。

pipesconduit这样的流数据库将允许您避免一次将整个文件读取到内存中,但据我所知 - Parsec不提供流接口来允许这种情况发生。 另一方面,Attoparsec确实包含这样的流接口,并且管道和导管都有Attoparsec适配器库(例如, Data.Conduit.Attoparsec).

dr:你可能只需要以下辅助函数:

import qualified Data.Text as T
import qualified Data.Text.IO as TIO
readFileStrict :: FilePath -> IO String
readFileStrict = fmap T.unpack . TIO.readFile

您可以使用 BangPatterns 语言扩展来强制实施 IO 操作的严格性,在本例中为 parseFromFile 。例如,函数hands可以在以下位置更改:

hands :: [String] → IO [Either ParseError [Hand]]
hands [] = return []
hands (f:fs) = do
  !res ← parseFromFile hand f
  others ← hands fs
  return (res:others)

此版本的 hands 在移动到列表中的下一个文件之前等待每次调用parseFromFile的结果。一旦有了这个,问题应该就消失了。一个完整的工作玩具示例是:

{-# LANGUAGE BangPatterns #-}
import Control.Monad
import Control.Applicative hiding (many)
import Data.Char (isDigit)
import System.Directory (getDirectoryContents)
import System.FilePath ((</>))
import Text.ParserCombinators.Parsec
data Hand = Hand Int deriving Show
hand :: GenParser Char st [Hand]
hand = do
  string "I'm file "
  num ← many digit
  newline
  eof
  return [Hand $ read num]
files :: IO [String]
files = map ("manyfiles" </>)
      ∘ filter (all isDigit) <$> getDirectoryContents "manyfiles"
hands :: [String] → IO [Either ParseError [Hand]]
hands [] = return []
hands (f:fs) = do
  !res ← parseFromFile hand f
  others ← hands fs
  return (res:others)
main :: IO 𐌏
main = do
  results ← files >≥ hands
  print results

最新更新