【问题标题】:Preventing "getCurrentDirectory: resource exhausted (Too many open files)" error防止“getCurrentDirectory:资源耗尽(打开的文件太多)”错误
【发布时间】:2014-04-06 11:08:10
【问题描述】:

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

files = getDirectoryContents historyFolder

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

注意:我的&lt;&lt;$&gt;&gt; 函数是这样的:

(<<$>>) :: (Functor f1, Functor f2) => (a -> b) -> f1 (f2 a) -> f1 (f2 b)
a <<$>> b = (a <$>) <$> b

【问题讨论】:

  • 问题是parseFromFile 太懒了,这是我建议改变的一点,因为你必须包含它。此外,使用pipesconduit 包可能是个好主意

标签: haskell lazy-evaluation parsec lazy-io


【解决方案1】:

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

pipesconduit 这样的流式数据库可以让您避免一次将整个文件读入内存,尽管据我所知,parsesec 没有提供流式接口来允许这种情况发生。另一方面,attoparsec 确实包含这样的流接口,并且管道和管道都有 attoparsec 适配器库(例如,Data.Conduit.Attoparsec)。

tl;dr:您可能只需要以下辅助函数:

import qualified Data.Text as T
import qualified Data.Text.IO as TIO

readFileStrict :: FilePath -> IO String
readFileStrict = fmap T.unpack . TIO.readFile

【讨论】:

  • parseFromFile 是 parsec 的一部分,可以是 lazy readByteString.Lazy 中的 readFile 或更多 strict variantbracket openBinaryFile ... hClose
【解决方案2】:

您可以使用 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

【讨论】:

    猜你喜欢
    • 2023-04-08
    • 1970-01-01
    • 1970-01-01
    • 2017-12-03
    • 1970-01-01
    • 2017-08-11
    • 1970-01-01
    • 1970-01-01
    • 2014-10-03
    相关资源
    最近更新 更多