問題描述
下面是Go 1.8 release notes中對該問題描述
The garbage collector no longer considers arguments live throughout the entirety of a function. For more information, and for how to force a variable to remain live, see the runtime.KeepAlive
function added in Go 1.7.
什么意思呢?就是說從1.8版本開始,go的GC不再管你變量在函數(shù)中的作用域了,只要GC發(fā)現(xiàn)后面這個變量沒有再被使用了,就回收。乍一看好像很有問題,仔細想似乎又沒什么問題。
緊接著官方對這個問題進行了一次update:
Updating: Code that sets a finalizer on an allocated object may need to add calls to runtime.KeepAlive
in functions or methods using that object. Read the KeepAlive
documentation and its example for more details.
Update的內(nèi)容對該問題的影響進行了具體說明,就是當用戶自己對一個對象設置了finalizer時,需要根據(jù)自己的需要來決定在哪個地方調(diào)用KeepAlive,以保證在此之前,GC不會回收該對象。就是說,雖然用戶設置了finalizer,但finalizer的觸發(fā)是由GC來控制的。新版本的GC是并行執(zhí)行的,且不關心變量在函數(shù)中的作用域了,因此很可能在你不希望他釋放的時候,GC把對象給釋放掉了。
解決方案
下面是官方給的一段典型的示例代碼:
type File struct { d int }
d, err := syscall.Open("/file/path", syscall.O_RDONLY, 0)
// ... do something if err != nil ...
p := &Fileu0z1t8os
runtime.SetFinalizer(p, func(p *File) { syscall.Close(p.d) })
var buf [10]byte
n, err := syscall.Read(p.d, buf[:])
解釋一下問題出在哪里:變量p創(chuàng)建之后,用戶給它設置了
finalizer。然后開始調(diào)用syscall.Read,注意Read傳的參數(shù)是p.d,Go中傳參都是復制的,也就是Read里面使用的變量跟變量p沒有半毛錢關系。也就是Read函數(shù)調(diào)用之后,結(jié)束之前,變量p就失去了存在的意義,GC會忠實的履行自己的職責,將p給回收,此時觸發(fā)了用戶自己設置的finalizer,將文件描述符給關閉了。但這個時候,Read操作可能還在通過該文件描述符訪問文件,這就是問題所在。
解決此問題的方法:
type File struct { d int }
d, err := syscall.Open("/file/path", syscall.O_RDONLY, 0)
// ... do something if err != nil ...
p := &Fileu0z1t8os
runtime.SetFinalizer(p, func(p *File) { syscall.Close(p.d) })
var buf [10]byte
n, err := syscall.Read(p.d, buf[:])
// Ensure p is not finalized until Read returns.
runtime.KeepAlive(p)
// No more uses of p after this point.
就是在Read函數(shù)調(diào)用之后,對對象p手動調(diào)用一次KeepAlive,KeepAlive是個空函數(shù),只是欺騙一下GC,變量p到這里還有被使用,在此之前不要回收。這就確保了Read返回前,對象p不會被回收。當然KeepAlive之后,p就會被GC回收。