4 回答
TA贡献1883条经验 获得超3个赞
确实,垃圾收集时文件会关闭,但是...正如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)
.
该配置文件包括堆栈跟踪。
TA贡献1884条经验 获得超4个赞
Go 应用程序应该显式释放资源。当变量超出范围时,没有语言功能会隐式释放资源。该语言确实提供了延迟功能,以便更轻松地编写显式代码。
正如 bserdar 和 VonC 所指出的,垃圾收集器有一个用于释放外部资源的钩子。该钩子由 os.File 类型使用。应用程序不应依赖此挂钩,因为在收集对象时未指定它(如果有的话)。
TA贡献1829条经验 获得超13个赞
当 os.File 被垃圾收集时,文件将自动关闭。这似乎是通过 SetFinalizer 调用完成的,因此文件最终将被关闭,而不是在它变得无法访问后立即关闭。
TA贡献1812条经验 获得超5个赞
在您的简单示例中,defer
不需要该语句。该文件在退出函数作用域时关闭。
但是,如果您返回该变量f
或将其保存在全局变量中,则其引用计数器会增加,因此不会被删除。
下面是一个示例,证明终结器以与初始化相反的顺序立即被调用(由于引用计数器达到零,因此立即被垃圾收集):
package main
import (
"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 int
func 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。有些情况下它不起作用。
- 4 回答
- 0 关注
- 139 浏览
添加回答
举报