什麼是 Go runtime-KeepAlive?
有些同學喜歡利用 runtime.SetFinalizer 模擬析構函數,當變量被回收時,執行一些回收操作,加速一些資源的釋放。在做性能優化的時候這樣做確實有一定的效果,不過這樣做是有一定的風險的。
比如下面這段代碼,初始化一個文件描述符,當 GC 發生時釋放掉無效的文件描述符。
type File struct { d int }
func main() {
p := openFile("t.txt")
content := readFile(p.d)
println("Here is the content: "+content)
}
func openFile(path string) *File {
d, err := syscall.Open(path, syscall.O_RDONLY, 0)
if err != nil {
panic(err)
}
p := &File{d}
runtime.SetFinalizer(p, func(p *File) {
syscall.Close(p.d)
})
return p
}
func readFile(descriptor int) string {
doSomeAllocation()
var buf [1000]byte
_, err := syscall.Read(descriptor, buf[:])
if err != nil {
panic(err)
}
return string(buf[:])
}
func doSomeAllocation() {
var a *int
// memory increase to force the GC
for i:= 0; i < 10000000; i++ {
i := 1
a = &i
}
_ = a
}
上面這段代碼是對 go 官方文檔 [1] 的一個延伸,doSomeAllocation 會強制執行 GC,當我們執行這段代碼時會出現下面的錯誤。
panic: no such file or directory
goroutine 1 [running]:
main.openFile(0x107a65e, 0x5, 0x10d9220)
main.go:20 +0xe5
main.main()
main.go:11 +0x3a
這是因爲 syscall.Open 產生的文件描述符比較特殊,是個 int 類型,當以值拷貝的方式在函數間傳遞時,並不會讓 File.d 產生引用關係,於是 GC 發生時就會調用 runtime.SetFinalizer(p, func(p *File)
導致文件描述符被 close 掉。
什麼是 runtime.KeepAlive ?
如上面的例子,我們如果才能讓文件描述符不被 gc 給釋放掉呢?其實很簡單,只需要調用 runtime.KeepAlive 即可。
func main() {
p := openFile("t.txt")
content := readFile(p.d)
runtime.KeepAlive(p)
println("Here is the content: "+content)
}
runtime.KeepAlive 能阻止 runtime.SetFinalizer 延遲發生,保證我們的變量不被 GC 所回收。
結論
正常情況下,runtime.KeepAlive,runtime.SetFinalizer 不應該被濫用,當我們真的需要使用時候,要注意使用是否合理。
《性能優化 | Go Ballast 讓內存控制更加絲滑》我們說到如何讓整個程序的聲明週期內維護一個 slice 不被 gc 給回收掉,這裏就用到了 runtime.KeepAlive。
這裏還有有一篇關於 runtime.KeepAlive 的文檔 [2],有興趣的可以看一下。
參考資料
[1]
go 官方文檔: https://pkg.go.dev/runtime#KeepAlive
[2]
文檔: https://medium.com/a-journey-with-go/go-keeping-a-variable-alive-c28e3633673a
本文由 Readfog 進行 AMP 轉碼,版權歸原作者所有。
來源:https://mp.weixin.qq.com/s/5XMmVqdcji8jpRnC2p0F3w