Go: 延長變量的生命週期

本文基於 Go 1.13。

在 Go 中,我們不需要自己管理內存分配和釋放。然而,有些時候我們需要對程序進行更細粒度的控制。Go 運行時提供了很多種控制運行時狀態及其與內存管理器之間相互影響的方式。本文中,我們來審查讓變量不被 GC 回收的能力。

場景

我們來看一個基於 Go 官方文檔 [1] 的例子:

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
}

源碼地址 [2]

這個程序中一個函數打開文件,另一個函數讀取文件。代表文件的結構體註冊了一個 finalizer,在 gc 釋放結構體時自動關閉文件。運行這個程序,會出現 panic:

panic: bad file descriptor

goroutine 1 [running]:
main.readFile(0x3, 0x5, 0xc000078008)
main.go:42 +0x103
main.main()
main.go:14 +0x4b
exit status 2

下面是流程圖:

圖 01

allocate 函數觸發 gc:

02.png

因爲文件描述符是個整型,並以副本傳遞,所以打開文件的函數返回的結構體 *File* 不再被引用。Gc 把它標記爲可以被回收的。之後觸發這個變量註冊的 finalizer,關閉文件。

然後,主協程讀取文件:

03.png

因爲文件已經被 finalizer 關閉,所以會出現 panic。

讓變量不被回收

runtime 包暴露了一個方法,用來在 Go 程序中避免出現這種情況,並顯式地聲明瞭讓變量不被回收。在運行到這個調用這個方法的地方之前,gc 不會清除指定的變量。下面是加了對這個方法的調用的新代碼:

04.png

在運行到 keepAlive 方法之前,gc 不能回收變量 p。如果你再運行一次程序,它會正常讀取文件併成功終止。

追本逐源

keepAlive 方法本身沒有做什麼:

05.png

運行時,Go 編譯器會以很多種方式優化代碼:函數內聯,死碼消除,等等。這個函數不會被內聯,Go 編譯器可以輕易地探測到哪裏調用了 keepAlive。編譯器很容易追蹤到調用它的地方,它會發出一個特殊的 SSA 指令,以此來確保它不會被 gc 回收。

06.png

在生成的 SSA 代碼中也可以看到這個 SSA 指令:

07.png

在我的文章 Go 編譯器概述 [3] 中你可以看到更多關於 Go 編譯器和 SSA 的信息。


via: https://medium.com/a-journey-with-go/go-keeping-a-variable-alive-c28e3633673a

作者:Vincent Blanchon[4] 譯者:lxbwolf[5] 校對:polaris1119[6]

本文由 GCTT[7] 原創編譯,Go 中文網 [8] 榮譽推出

參考資料

[1]

Go 官方文檔: https://golang.org/pkg/runtime/#KeepAlive

[2]

源碼地址: https://gist.githubusercontent.com/blanchonvincent/a247b6c2af559b62f93377b5d7581b7f/raw/6488ec2a36c28c46f942b7ac8f24af4e75c19a2f/main.go

[3]

Go 編譯器概述: https://medium.com/a-journey-with-go/go-overview-of-the-compiler-4e5a153ca889

[4]

Vincent Blanchon: https://medium.com/@blanchon.vincent

[5]

lxbwolf: https://github.com/lxbwolf

[6]

polaris1119: https://github.com/polaris1119

[7]

GCTT: https://github.com/studygolang/GCTT

[8]

Go 中文網: https://studygolang.com/

本文由 Readfog 進行 AMP 轉碼,版權歸原作者所有。
來源https://mp.weixin.qq.com/s/S_lO8jQY0TGS7_zvDIUcfw