
本文转载自微信公众号「HHFCodeRv」,聊聊作者haohongfan。聊聊转载本文请联系HHFCodeRv公众号。聊聊
有些同学喜欢利用 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 不应该被滥用,当我们真的需要使用时候,要注意使用是否合理。