猿问

如果没有显式关闭,Go 会自动关闭资源吗?

以下内容Open后跟 deferredClose在 Go 中是惯用的:


func example() {

    f, err := os.Open(path)

    if err != nil {

        return

    }

    defer f.Close()

}

如果我没有会怎样defer f.Close()?


当我调用此函数并f超出范围时,它会自动关闭文件还是我有僵尸文件句柄?


如果它自动关闭,具体什么时候关闭?


泛舟湖上清波郎朗
浏览 140回答 4
4回答

白板的微信

确实,垃圾收集时文件会关闭,但是...正如Alexander Morozov的“ Go 中终结器之谜”中提到的 - LK4D4math :在 Go 中,我们同时拥有 GC 和专业用户:)因此,在我看来,显式调用Close总是比魔术终结器更好。亚历山大补充道:终结器的问题在于您无法控制它们,而且更重要的是,您并不期望它们。看这段代码:func getFd(path string) (int, error) {    f, err := os.Open(path)    if err != nil {        return -1, err    }    return f.Fd(), nil}当你为 Linux 编写一些东西时,从路径获取文件描述符是非常常见的操作。但该代码是不可靠的,因为当您从 , 返回时getFd(),f会丢失其最后一个引用,因此您的文件注定迟早会被关闭(当下一个 GC 周期到来时)。在这里,问题不在于文件将被关闭,而在于它没有记录并且根本不是预期的。有人提议扩展终结器并检测泄漏(例如文件描述符泄漏)但是……拉斯·考克斯令人信服地推翻了这一点:任何对此主题感兴趣的人都应该阅读 Hans Boehm 的论文“析构函数、终结器和同步”。它极大地影响了我们尽可能限制 Go 中终结器范围的决定。它们是允许与关联堆内存同时回收非(堆内存)资源的必要之恶,但它们本质上比大多数人最初认为的更有限。我们不会扩展终结器的范围,无论是在实现中还是在标准库中,或者在 x 存储库中,我们也不会鼓励其他人扩展该范围。如果您想跟踪手动管理的对象,最好使用runtime/pprof.NewProfile.例如,在 Google 的源代码树中,我们有一个 Google 范围内的“文件”抽象,并且 Go 包装器包声明了一个全局的:var profiles = pprof.NewProfile("file")创建新文件的函数末尾显示:profiles.Add(f, 2) return f然后f.Close_profiles.Remove(f)然后我们可以获得所有正在使用的文件的配置文件,无论是“泄露”还是其他方式,来自/debug/pprof/file或来自pprof.Lookup("file").WriteTo(w, 0).该配置文件包括堆栈跟踪。

慕村9548890

Go 应用程序应该显式释放资源。当变量超出范围时,没有语言功能会隐式释放资源。该语言确实提供了延迟功能,以便更轻松地编写显式代码。正如 bserdar 和 VonC 所指出的,垃圾收集器有一个用于释放外部资源的钩子。该钩子由 os.File 类型使用。应用程序不应依赖此挂钩,因为在收集对象时未指定它(如果有的话)。

烙印99

当 os.File 被垃圾收集时,文件将自动关闭。这似乎是通过 SetFinalizer 调用完成的,因此文件最终将被关闭,而不是在它变得无法访问后立即关闭。

慕雪6442864

在您的简单示例中,defer不需要该语句。该文件在退出函数作用域时关闭。但是,如果您返回该变量f或将其保存在全局变量中,则其引用计数器会增加,因此不会被删除。下面是一个示例,证明终结器以与初始化相反的顺序立即被调用(由于引用计数器达到零,因此立即被垃圾收集):package mainimport (        "fmt"        "runtime"        "time")type Foo struct {        name    string        num     int}func  finalizer(f *Foo) {        fmt.Println("a finalizer has run for ", f.name, f.num)}var counter intfunc MakeFoo(name string) (a_foo *Foo) {        a_foo = &Foo{name, counter}        counter++        runtime.SetFinalizer(a_foo, finalizer)        return}func Bar() {        f1 := MakeFoo("one")        f2 := MakeFoo("two")        fmt.Println("f1 is: ", f1.name)        fmt.Println("f2 is: ", f2.name)}func main() {        for i := 0; i < 3; i++ {                Bar()                time.Sleep(time.Second)                runtime.GC()        }        fmt.Println("done.")}事实上,您无法真正控制终结器是否被调用。但是,如果您确保不将局部变量复制到任何地方,那么它会立即发生。实际上,一个有趣的事实是,如果您使用 defer,那么您将创建对该对象的引用(您需要有一个引用才能Close()在退出函数时调用该对象)。这是非常重要的一点,因为如果有循环,实际上会使情况变得更糟:    ...    for i:=0; i<10; i++ {        f := os.Open(...)        defer f.Close()        ...    } // without the defer, the file is closed here, as expected} // <- defer is called here, so you will open 10 files  // and keep all 10 open until this line所以你必须非常小心defer。有些情况下它不起作用。
随时随地看视频慕课网APP

相关分类

Go
我要回答