Go的ioutil包是否会使用过多的文件描述符或者发生泄漏问题?

huangapple go评论161阅读模式
英文:

Go ioutil using too many file descriptors/leak?

问题

我正在遍历一个文件列表,并将其中的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并查看了ioutil源码http://golang.org/src/pkg/io/ioutil/ioutil.go之后,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个文件描述符。 Go的ioutil包是否会使用过多的文件描述符或者发生泄漏问题?

我用上面显示的显式Close()替换了延迟的Close,但仍然收到相同的“打开的文件太多”错误。我的修改后的解压函数有问题吗?

更新 #2
糟糕,我在Heroku上运行这个程序时,一直在向错误的应用程序推送我的更改。教训是:在Heroku工具包中验证目标应用程序。

来自https://gist.github.com/hnaohiro/4572580的解压缩代码不起作用,因为它直到处理完所有文件后才关闭文件。

我上面显示的显式关闭版本和@peterSO答案中的延迟版本都有效。

英文:

I am going through a list of files and Unmarshalling the xml data in them into an array of structs rArray. I intend to process about 18000 files. When I get to about 1300 files processed, the program panics and says that too many files are open. If I limit the amount of files processed to a safe amount of 1000, the program does not crash.

<s>As seen below, I am using ioutil.ReadFile to read the file data.

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

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

I am not sure if Go is using too many file descriptors or not closing the files fast enough.

After reading https://groups.google.com/forum/#!topic/golang-nuts/7yXXjgcOikM and viewing the ioutil source in http://golang.org/src/pkg/io/ioutil/ioutil.go, the code for ioutil.ReadFile shows that it uses defer to close the file. defer runs when calling function is returned and ReadFile() is the calling function. Am I correct in this understanding?
I also tried wrapping the ioutil.ReadFile part of my code in a function, but it makes no difference.

My ulimit is set to unlimited. </s>

UPDATE:
I believe that the error of too many files is actually occurring during my Unzip function.

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
}

I initially got the Unzip function from https://gist.github.com/hnaohiro/4572580, but upon further inspection, the use of defer in the gist author's function seemed wrong as the file would only be closed after the Unzip() function returned which is too late becuase then 18000 file descriptors will be open. Go的ioutil包是否会使用过多的文件描述符或者发生泄漏问题?

<s>I replaced the deferred Closes with explicit Close() as shown above, but am still getting the same "too many open files" error. Is there a problem with my modified Unzip function?</s>

UPDATE # 2
Oops, I was running this on Heroku and was pushing to the wrong app with my changes this entire time. Lesson learned: verify target app in heroku toolbelt.

Unzip code from https://gist.github.com/hnaohiro/4572580 does not work as it does not close files until all files processed.

My unzip code with explicit close above works and so does the defer version in @peterSO's answer.

答案1

得分: 3

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

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)
	}
}

请注意,这只是对Unzip函数的修改,其他部分保持不变。

英文:

I would modify the Unzip function from https://gist.github.com/hnaohiro/4572580 to the following:

package main
import (
&quot;archive/zip&quot;
&quot;io&quot;
&quot;log&quot;
&quot;os&quot;
&quot;path/filepath&quot;
)
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(&quot;./sample.zip&quot;, &quot;./out&quot;)
if err != nil {
log.Fatal(err)
}
}

huangapple
  • 本文由 发表于 2014年6月13日 10:57:17
  • 转载请务必保留本文链接:https://go.coder-hub.com/24197011.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定