垃圾收集器和延迟函数之间的冲突?

bud*_*123 2 garbage-collection go

请考虑以下代码段:

func a(fd int) {
 file := os.NewFile(uintptr(fd), "")
 defer func() {
   if err := file.Close(); err != nil { 
      fmt.Printf("%v", err)
   }
}
Run Code Online (Sandbox Code Playgroud)

这段代码是合法的,并且可以正常工作.返回时文件将被关闭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)
   }
}
Run Code Online (Sandbox Code Playgroud)

bad file descriptor由于NewFile设置了一个终结 器,在垃圾收集期间将关闭文件本身,因此偶尔会收到错误.

我不清楚的是,延迟函数仍然有对文件的引用,所以从理论上讲,它不应该是垃圾收集.那么golang运行时为什么会这样呢?

zzn*_*zzn 5

代码的问题是在file.Fd()返回后,file无法访问,因此file可能会被终结器关闭(垃圾回收).

根据runtime.SetFinalizer:

例如,如果p指向包含文件描述符d的结构,并且p具有关闭该文件描述符的终结器,并且如果函数中最后一次使用p是对syscall.Write的调用(pd,buf,size ),一旦程序进入syscall.Write,则p可能无法访问.终结器可能会在那一刻运行,关闭pd,导致syscall.Write失败,因为它正在写入一个已关闭的文件描述符(或者更糟糕的是,由一个不同的goroutine打开的完全不同的文件描述符).要避免此问题,请在调用syscall.Write之后调用runtime.KeepAlive(p).

runtime.KeepAlive用法:

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)
    }()
 }
Run Code Online (Sandbox Code Playgroud)