英文:
Collision between garbage collector and deferred functions?
问题
考虑以下代码片段:
func a(fd int) {
file := os.NewFile(uintptr(fd), "")
defer func() {
if err := file.Close(); err != nil {
fmt.Printf("%v", err)
}
}
}
这段代码是合法的,并且可以正常工作。在从 a()
返回时,文件将被关闭。
然而,以下代码将无法正常工作:
func a(fd int) {
file := os.NewFile(uintptr(fd), "")
defer func() {
if err := syscall.Close(int(file.Fd())); err != nil {
fmt.Printf("%v", err)
}
}
}
偶尔会收到 bad file descriptor
的错误,这是因为 NewFile 设置了一个 finalizer,在垃圾回收期间会关闭文件本身。
我不清楚的是,延迟函数仍然引用着文件,理论上它不应该被垃圾回收。那么为什么 Golang 运行时会表现出这种行为呢?
英文:
Consider the following code snippet:
func a(fd int) {
file := os.NewFile(uintptr(fd), "")
defer func() {
if err := file.Close(); err != nil {
fmt.Printf("%v", err)
}
}
This piece of code is legit, and will work OK. Files will be closed upon returning from a()
However, The following will not work correctly:
func a(fd int) {
file := os.NewFile(uintptr(fd), "")
defer func() {
if err := syscall.Close(int(file.Fd()); err != nil {
fmt.Printf("%v", err)
}
}
The error that will be received, occasionally, will be bad file descriptor
, due to the fact of NewFile setting a finalizer
which, during garbage collection, will close the file itself.
Whats unclear to me, is that the deferred function still has a reference to the file, so theoretically, it shouldn't be garbage collected yet.
So why is golang runtime behaves that way?
答案1
得分: 5
代码的问题是在file.Fd()
返回之后,file
变量无法访问,所以file
可能会被终结器(垃圾回收器)关闭。
根据runtime.SetFinalizer的说明:
例如,如果p指向一个包含文件描述符d的结构体,并且p有一个关闭该文件描述符的终结器,如果p在函数中的最后一次使用是对syscall.Write(p.d, buf, size)的调用,那么一旦程序进入syscall.Write,p可能就无法访问了。终结器可能会在那时运行,关闭p.d,导致syscall.Write失败,因为它正在向一个已关闭的文件描述符(或者更糟糕的是,向一个由不同的goroutine打开的完全不同的文件描述符)写入。为了避免这个问题,在调用syscall.Write之后调用runtime.KeepAlive(p)。
KeepAlive将其参数标记为当前可访问。这确保在调用KeepAlive的程序点之前,对象不会被释放,其终结器不会运行。
func a(fd int) {
file := os.NewFile(uintptr(fd), "")
defer func() {
if err := syscall.Close(int(file.Fd())); err != nil {
fmt.Printf("%v", err)
}
runtime.KeepAlive(file)
}()
}
英文:
the problems of the code is after file.Fd()
return, file
is unreachable, so file
may be close by the finalizer(garbage collected).
according to runtime.SetFinalizer:
> For example, if p points to a struct that contains a file descriptor d, and p has a finalizer that closes that file descriptor, and if the last use of p in a function is a call to syscall.Write(p.d, buf, size), then p may be unreachable as soon as the program enters syscall.Write. The finalizer may run at that moment, closing p.d, causing syscall.Write to fail because it is writing to a closed file descriptor (or, worse, to an entirely different file descriptor opened by a different goroutine). To avoid this problem, call runtime.KeepAlive(p) after the call to syscall.Write.
runtime.KeepAlive usage:
>KeepAlive marks its argument as currently reachable. This ensures that the object is not freed, and its finalizer is not run, before the point in the program where KeepAlive is called.
func a(fd int) {
file := os.NewFile(uintptr(fd), "")
defer func() {
if err := syscall.Close(int(file.Fd()); err != nil {
fmt.Printf("%v", err)
}
runtime.KeepAlive(file)
}()
}
通过集体智慧和协作来改善编程学习和解决问题的方式。致力于成为全球开发者共同参与的知识库,让每个人都能够通过互相帮助和分享经验来进步。
评论