2017-05-09 39 views
0

考慮下面的代碼片段:垃圾回收器和延遲函數之間的衝突?

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 setting a finalizer 的這一事實,垃圾回收過程中,將關閉文件本身。

對我而言還不清楚的是,延遲函數仍然有一個對文件的引用,所以理論上它不應該被垃圾收集。 那麼,爲什麼golang運行時的行爲就是這樣呢?

+0

相關的/可能的duplicatr [在Go中,當將一個變量變得不可達?](http://stackoverflow.com/questions/37588639/in-go-when-will-a-variable-成爲不可達/ 37591282#37591282) – icza

回答

4

該代碼的問題是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的程序點之前,該對象不會被釋放,並且其終結器不會運行。

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) 
    }() 
}