【问题标题】:Go ioutil using too many file descriptors/leak?使用太多文件描述符/泄漏去 ioutil?
【发布时间】:2014-06-13 02:57:17
【问题描述】:

我正在浏览文件列表并将其中的 xml 数据解组为结构数组rArray。我打算处理大约 18000 个文件。当我处理了大约 1300 个文件时,程序会出现恐慌并说打开了太多文件。如果我将处理的文件数量限制为 1000 个安全数量,则程序不会崩溃。

如下所示,我使用ioutil.ReadFile 来读取文件数据。

for _, f := range files {

    func() {
        data, err := ioutil.ReadFile("./" + recordDir + "/" + f.Name())
        if err != nil {
            fmt.Println("error reading %v", err)
            return
        } else {
            if (strings.Contains(filepath.Ext(f.Name()), "xml")) {

                //unmarshal data and put into struct array
                err = xml.Unmarshal([]byte(data), &rArray[a])
                if err != nil {
                    fmt.Println("error decoding %v: %v",f.Name(), err)
                    return
                }
            }
        }
    }()
}

我不确定 Go 是否使用了太多的文件描述符或没有足够快地关闭文件。

阅读https://groups.google.com/forum/#!topic/golang-nuts/7yXXjgcOikM并查看http://golang.org/src/pkg/io/ioutil/ioutil.go中的ioutil源代码后,ioutil.ReadFile的代码显示它使用defer关闭文件。 defer 在调用函数返回时运行,ReadFile() 是调用函数。我的理解正确吗? 我还尝试将我的代码的ioutil.ReadFile 部分包装在一个函数中,但这没有任何区别。

我的ulimit 设置为无限制。

更新: 我相信太多文件的错误实际上是在我的解压缩功能期间发生的。

func Unzip(src, dest string) error {
    r, err := zip.OpenReader(src)
    if err != nil {
        return err
    }

    for _, f := range r.File {
        rc, err := f.Open()
        if err != nil {
            panic(err)
        }

        path := filepath.Join(dest, f.Name)
        if f.FileInfo().IsDir() {
            os.MkdirAll(path, f.Mode())
        } else {
            f, err := os.OpenFile(
                path, os.O_WRONLY|os.O_CREATE|os.O_TRUNC, f.Mode())
            if err != nil {
                panic(err)
            }

            _, err = io.Copy(f, rc)
            if err != nil {
                panic(err)
            }
            f.Close()
        }
        rc.Close()
    }
    r.Close()
    return nil
}

我最初从https://gist.github.com/hnaohiro/4572580 获得了Unzip 函数,但经过进一步检查,在主旨作者的函数中使用defer 似乎是错误的,因为只有在返回Unzip() 函数后才会关闭文件,即为时已晚,因为届时将打开 18000 个文件描述符。 ;)

我用明确的Close() 替换了延迟的Closes,如上所示,但仍然收到相同的“打开的文件太多”错误。我修改后的解压功能有问题吗?

更新 #2 糟糕,我在 Heroku 上运行它,并且一直在将我的更改推送到错误的应用程序。经验教训:在 heroku 工具带中验证目标应用程序。

https://gist.github.com/hnaohiro/4572580 解压缩代码起作用,因为它在处理完所有文件之前不会关闭文件。

上面明确关闭的我的解压缩代码有效,@peterSO 答案中的延迟版本也是如此。

【问题讨论】:

  • 这很奇怪,什么版本的 go (go version)?用f, err := os.Open(....) / defer f.Close() / ioutil.ReadAll 替换ioutil.ReadFile 也有帮助吗?
  • @OneOfOne,我将原因缩小到我拥有的另一个功能,但仍然遇到上面更新中描述的问题。我目前正在使用 go 1.2.2。谢谢。
  • 也许可以查看/proc/$pid/fd 以查看应用程序中打开了哪些文件(它应该包含代表每个打开文件描述符的符号链接)。这可能表明哪些描述符正在泄漏。
  • 你是否对每个文件都使用了 goroutine?
  • 是的,我认为问题在于打开文件的 goroutine 过多并屈服于调度程序。代码逻辑是什么?

标签: file-io go unmarshalling deferred ulimit


【解决方案1】:

我会将 Unzip 函数从 https://gist.github.com/hnaohiro/4572580 修改为以下内容:

package main

import (
    "archive/zip"
    "io"
    "log"
    "os"
    "path/filepath"
)

func unzipFile(f *zip.File, dest string) error {
    rc, err := f.Open()
    if err != nil {
        return err
    }
    defer rc.Close()

    path := filepath.Join(dest, f.Name)
    if f.FileInfo().IsDir() {
        err := os.MkdirAll(path, f.Mode())
        if err != nil {
            return err
        }
    } else {
        f, err := os.OpenFile(
            path, os.O_WRONLY|os.O_CREATE|os.O_TRUNC, f.Mode())
        if err != nil {
            return err
        }
        defer f.Close()

        _, err = io.Copy(f, rc)
        if err != nil {
            return err
        }
    }
    return nil
}

func Unzip(src, dest string) error {
    r, err := zip.OpenReader(src)
    if err != nil {
        return err
    }
    defer r.Close()

    for _, f := range r.File {
        err := unzipFile(f, dest)
        if err != nil {
            return err
        }
    }

    return nil
}

func main() {
    err := Unzip("./sample.zip", "./out")
    if err != nil {
        log.Fatal(err)
    }
}

【讨论】:

  • 嗯,这行得通(以及我之前的显式版本),但您能否解释一下为什么unzipFile 方法不能保持打开 N 个文件描述符?我以为unzipFile 中的defers 只会在 unzipFile 返回时运行?我对此很感兴趣。谢谢!
  • 非常好的分离 unzipFile - 它确保每个文件都能快速关闭。
猜你喜欢
  • 2015-09-23
  • 1970-01-01
  • 1970-01-01
  • 2011-01-15
  • 2014-09-20
  • 2020-01-05
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多