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

时间:2017-05-09 05:00:30

标签: go garbage-collection

请考虑以下代码段:

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运行时为什么会这样呢?

1 个答案:

答案 0 :(得分:5)

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

根据runtime.SetFinalizer

  

例如,如果p指向包含文件描述符d的结构,并且p具有关闭该文件描述符的终结器,并且如果函数中最后一次使用p是对syscall.Write的调用(pd, buf,size),一旦程序进入syscall.Write,则p可能无法访问。终结器可能会在那一刻运行,关闭p.d,导致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)
    }()
 }