Go 1.12中值得關注的幾個變化

choubou發表於2021-09-09

Go team如期在2月末版本。從Go 1.12的粗略來看,這個版本相較於之前增加了、的,變化略“小”。這也給下一個版本預留了足夠的“驚喜”空間:)。從目前的plan來看,Go 1.13很可能落地的包括:Go2的幾個proposals:, 和等,以及等。

言歸正傳,我們來看看Go 1.12版本中值得我們關注的幾個變化。

一. Go 1.12的可移植性

Go 1.12一如既往的保持了,使用Go 1.12編譯以往編寫的遺留程式碼,理論上都可以編譯透過並正常執行起來。這是很難得的,尤其是在"Go2"有關proposal逐步落地的“時間節點”,想必Go team為了保持Go1付出了不少額外的努力。

Go語言具有超強的。在Go 1.12中,Go又增加了對aix/ppc64、windows/arm的支援,我們可以在執行於樹莓派3的Windows 10 IoT Core上執行Go程式了。

但是對於一些較老的平臺系統,Go也不想背上較重的包袱。Go也在逐漸“放棄”一些老版本的系統,比如Go 1.12是最後一個支援macOS 10.10、FreeBSD 10.x的版本。在我的一臺Mac 10.9.2的老機器上執行go 1.12將會得到下面錯誤:


$./go version
dyld: Symbol not found: _unlinkat
  Referenced from: /Users/tony/.bin/go1.12/bin/./go
  Expected in: flat namespace

[1]    2403 trace trap  ./go version

二. Go modules機制的最佳化

1. GO111MODULE=on時,獲取go module不再顯式需要go.mod

用過Go 1.11 的童鞋可能都遇到過這個問題,那就是在GO111MODULE=on的情況下(非GOPATH路徑),我要go get某個package時,如果compiler沒有在適當位置找到go.mod,就會提示如下錯誤:


//go 1.11.2

# go get github.com/bigwhite/gocmpp
go: cannot find main module; see 'go help modules'

或

# go get github.com/bigwhite/gocmpp
go: cannot determine module path for source directory /Users/tony/test/go (outside GOPATH, no import comments)

這顯然非常不方便。為了go get 一個package,我還需要顯式地建立一個go.mod檔案。在Go 1.12版本中,這個問題被最佳化掉了。


//go 1.12

# go get github.com/bigwhite/gocmpp
go: finding github.com/bigwhite/gocmpp latest
go: finding golang.org/x/text/encoding/unicode latest
go: finding golang.org/x/text/transform latest
go: finding golang.org/x/text/encoding/simplifiedchinese latest
go: finding golang.org/x/text/encoding latest
go: downloading golang.org/x/text v0.3.0
go: extracting golang.org/x/text v0.3.0

其他在go 1.11.x中對go.mod顯式依賴的命令,諸如go list、go mod download也在Go 1.12版本中和go get一樣不再顯式依賴go.mod。

並且在Go 1.12中go module的下載、解壓操作支援併發進行,前提是go module的Cache路徑:$GOPATH/pkg/mod必須在一個支援file locking的檔案系統中。

2. go.mod中增加go指示欄位(go directive)

go 1.12版本在go.mod檔案中增加了一個go version的指示欄位,用於指示該module內原始碼所使用的 go版本。使用go 1.12建立的go.mod類似下面這樣:


# go mod init github.com/bigwhite/test
go: creating new go.mod: module github.com/bigwhite/test
# cat go.mod
module github.com/bigwhite/test

go 1.12

按照release notes中的說法,如果go.mod中go指示器指示的版本高於你使用的go tool鏈版本,那麼go也會嘗試繼續編譯。如果編譯成功了,那也是ok的。但是如果編譯失敗,那麼會提示:module編譯需要更新版本的go tool鏈。

我們使用go 1.11.4版本go module的程式碼:


// main.go

package main

import (
    "fmt"
)

func main() {
    fmt.Println("go world")
}

# go build main.go
# ./main
go world

我們看到,雖然go tool chain版本是1.11.4,低於go.mod中的go 1.12,但go 1.11.4仍然嘗試繼續編譯程式碼,並且順利透過。

如果我們將程式碼“故意”修改為下面這樣:


//main.go

package main

import (
        "fmt"
)

func main() {
        fmt.Printl("go world") // 這裡我們故意將Println寫成Printl
}

再用go 1.11.4編譯這段程式碼:


# go build main.go
# command-line-arguments
./main.go:8:2: undefined: fmt.Printl
note: module requires Go 1.12

我們看到go 1.11.4 compiler提示“需要go 1.12"版本編譯器。從這裡我們看出,我們可以使用go指示器用作module最低version約束的標識。在沒有go指示器時,我們只能在文件上顯式增加這種約束的描述。

不過,這裡有一個小插曲,那就是這種不管go.mod中go版本號是多少,仍然嘗試繼續編譯的機制僅適用於go 1.11.4以及後續高版本。從引入go module的go 1.11到go 1.11.3目前都還不支援這種機制,如果用go 1.11.3嘗試編譯以下上面的程式碼,會得到如下結果:


# go build main.go
go build command-line-arguments: module requires Go 1.12

go 1.11.3不會繼續嘗試編譯,而是在對比當前go tool chain版本與go.mod中go指示器的version後,給出了錯誤的提示並退出。

如果非要使用低於go 1.11.4版本的編譯器去編譯的話,我們可以使用go 1.12工具鏈的go mod edit -go命令來修改一下go.mod中的版本為go 1.11。然後再用go 1.11.4以下的版本去編譯:


# go mod edit -go=1.11
# cat go.mod
module github.com/bigwhite/test

go 1.11

# go build main.go  //使用go 1.11.3編譯器

這樣,我們就可用go 1.11~go 1.11.3正常編譯原始碼了。

三. 對binary-only package的最後支援

我在2015的一篇文章 《》中提及到Go的編譯對原始碼的依賴性。對於開源工程中的包,這完全不是問題。但是對於一些商業公司而言,原始碼是公司資產,是不能作為交付物提供給買方的。為此,Go team在中增加了對的機制。

所謂"binary-only package"就是允許開發人員釋出不包含原始碼的二進位制形式的package,並且可直接基於該二進位制package進行編譯。比如下面這個例子:


// 建立二進位制package

# cat $GOPATH/src/github.com/bigwhite/foo.go
package foo

import "fmt"

func HelloGo() {
    fmt.Println("Hello,Go")
}


# go build -o  $GOPATH/pkg/linux_amd64/github.com/bigwhite/foo.a

# ls $GOPATH/pkg/linux_amd64/github.com/bigwhite/foo.a
/root/.go/pkg/linux_amd64/github.com/bigwhite/foo.a

# mkdir temp
# mv foo.go temp

# touch foo.go

# cat foo.go

//go:binary-only-package

package foo

import "fmt"

# cd $GOPATH

# zip -r foo-binary.zip src/github.com/bigwhite/foo/foo.go pkg/linux_amd64/github.com/bigwhite/foo.a
updating: pkg/linux_amd64/github.com/bigwhite/foo.a (deflated 42%)
  adding: src/github.com/bigwhite/foo/foo.go (deflated 11%)

我們將foo-binary.zip釋出到目標機器上後,進行如下操作:


# unzip foo-binary.zip -d $GOPATH/
Archive:  foo-binary.zip
  inflating: /root/.go/pkg/linux_amd64/github.com/bigwhite/foo.a
  inflating: /root/.go/src/github.com/bigwhite/foo/foo.go

接下來,我們就基於二進位制的foo.a來編譯依賴它的包:


//$GOPATH/src/bar.go

package main

import "github.com/bigwhite/foo"

func main() {
        foo.HelloGo()
}

# go build -o bar bar.go
# ./bar
Hello,Go

但是經過幾個版本的迭代,Go team發現:,無法保證binary-only package的編譯使用的是與最終連結時相同的依賴版本,這很可能會造成因記憶體問題而導致的崩潰。並且經過調查,似乎用binary-only package的gopher並不多,並且gopher可以使用plugin、shared library、c-shared library等來替代binary-only package,以避免原始碼分發。於是Go 1.12版本將成為支援binary-only package的最後版本。

四. 執行時與標準庫

經過~對執行時,尤其是GC的大幅最佳化和改善後,Go 1.11、Go 1.12對執行時的改善相比之下都是小幅度的。

在Go 1.12中,一次GC後的記憶體分配延遲得以改善,這得益於在大量heap依然存在時清理效能的提升。執行時也會更加積極地將釋放的記憶體歸還給作業系統,以應對大塊記憶體分配無法重用已存在的堆空間的問題。在linux上,執行時使用MADV_FREE釋放未使用的記憶體,這更為高效,作業系統核心可以在需要時重用這些記憶體。

在多CPU的機器上,執行時的timer和deadline程式碼執行效能更高了,這對於提升網路連線的deadline效能大有裨益。

標準庫最大的改變應該算是對的支援了。不過預設不開啟。Go 1.13中將成為預設開啟功能。大多數涉及TLS的程式碼無需修改,使用Go 1.12重新編譯後即可無縫支援TLS 1.3。

另一個”有趣“的變化是syscall包增加了Syscall18,依據syscall包中函式名字慣例,Syscall18支援最多傳入18個引數,這個函式的引入是為了Windows準備的。現在少有程式設計師會設計包含10多個引數的函式或方法了,這估計也是為了滿足Windows中“遺留程式碼”的需求。

五. 工具鏈及其他

1. go安裝包中移除go tour

go tour被從go的安裝包中移除了,Go的安裝包從go 1.4.x開始到go 1.11.x變得日益“龐大”:以linux/amd64的tar.gz包為例,變化趨勢如下:

go 1.4.3:  53MB
go 1.5.4:  76MB
go 1.6.4:  83MB
go 1.7.6:  80MB
go 1.8.7:  96MB
go 1.9.7:  113MB
go 1.10.8: 97MB
go 1.11.5: 134MB
go 1.12:   121MB

後續預計會有更多的非核心功能將會從go安裝包中移除來對Go安裝包進行瘦身,即便不能瘦身,也至少要保持在現有的size水平上。

本次go tour被挪到:


# go get -u golang.org/x/tour

# tour //啟動tour

Go 1.12也是godoc作為web server被內建在Go安裝包的最後一個版本,在Go 1.13中該工具也會被從安裝包中剔除,如有需要,可像go tour一樣透過go get來單獨安裝。

2. Build cache成為必需

build cache在被引入以加快Go包編譯構建速度,但是在Go 1.10和中都可以使用GOCACHE=off關閉build cache機制。但是在Go 1.12中build cache成為必需。如果設定GOCACHE=off,那麼編譯器將報錯:


# GOCACHE=off  go build github.com/bigwhite/gocmpp
build cache is disabled by GOCACHE=off, but required as of Go 1.12

3. Go compiler支援-lang flag

Go compiler支援-lang flag,可以指示編譯過程使用哪個版本的Go語法(注意不包括標準庫變化等,僅限於語言自身語法)。比如:


//main.go

package main

import "fmt"

type Int = int

func main() {
        var a Int = 5
        fmt.Println(a)
}


# go run main.go
5

上面是一個使用了才引入的type alias語法的Go程式碼,我們使用Go 1.12可以正常編譯執行它。但是如果我使用-lang flag,指定使用go1.8語法編譯該程式碼,我們會得到如下錯誤提示:


# go build  -gcflags "-lang=go1.8" main.go
# command-line-arguments
./main.go:5:6: type aliases only supported as of -lang=go1.9

換成-lang=go1.9就會得到正確結果:


# go build  -gcflags "-lang=go1.9" main.go
# ./main
5


講師主頁:
實戰課:
免費課:

來自 “ ITPUB部落格 ” ,連結:http://blog.itpub.net/4548/viewspace-2821838/,如需轉載,請註明出處,否則將追究法律責任。

相關文章