请考虑以下代码段:
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)
}
}
由于NewFile setting a finalizer的事实,偶尔会收到的错误为bad file descriptor
在垃圾收集期间,它将关闭文件本身。
我不清楚的是,延迟函数仍然有对文件的引用,所以从理论上讲,它不应该是垃圾收集。 那么golang运行时为什么会这样呢?
答案 0 :(得分:5)
代码问题在file.Fd()
返回后,file
无法访问,因此file
可能会被终结器关闭(垃圾回收)。
例如,如果p指向包含文件描述符d的结构,并且p具有关闭该文件描述符的终结器,并且如果函数中最后一次使用p是对syscall.Write的调用(pd, 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)
}()
}