Go 1.22中值得關(guān)注的幾個變化,你知道幾個?
美國時間2024年2月6日,正當(dāng)中國人民洋溢在即將迎來龍年春節(jié)的喜慶祥和的氣氛中時,Eli Bendersky[1]代表Go團隊在Go官博發(fā)文“Go 1.22 is released![2]”,正式向世界宣告了Go 1.22版本的發(fā)布!

注:大家可以從Go官網(wǎng)下載Go 1.22的第一個版本go 1.22.0,也可以在Go playground[3]上選擇Go 1.22版本在線體驗Go 1.22的語法。
記憶中,這似乎是Eli Bendersky首次代表Go團隊撰寫Go版本發(fā)布的文章,文章短小且言簡意賅,會讓大家誤以為Go 1.22版本沒有太多的功能點變更,其實不然。讀過我之前寫的“Go 1.22新特性前瞻[4]”一文的童鞋都知道Go 1.22中有很多重要且影響深遠的值得我們關(guān)注的變化。在這篇文章中,我們就再來介紹一下這些變化,供大家參考。
0. 插播“舊聞”:Go再次進入Top10,并刷新有史以來的最高排名
TIOBE編程語言排行榜發(fā)布2024年2月編程語言排名的時間恰逢中國人民的傳統(tǒng)佳節(jié)春節(jié)期間,因此它的這次排名發(fā)布“淹沒”在了“龍年大吉”的喜慶氣氛當(dāng)中了。年后開工,大家翻看這條“舊聞”時,才發(fā)現(xiàn)在這次排名中,Go再一次回到Top10,位列第8名,刷新了Go打榜一來的歷史最好位次。

單看這一次進入top10似乎沒有什么,因為2023年4月份,Go也躋身過top10,排名第10。但如果從Go打榜以來的歷史曲線來看,如下圖:

我們看到了“翹尾”,我們看到了Go邁過“低谷”后的爬升!這與我在《Go語言第一課專欄》[5]的結(jié)課語《和你一起迎接Go的黃金十年》[6]中預(yù)判:Go即將迎來自己的黃金十年 愈來愈吻合了!
不過,我在《2023年Go語言盤點:穩(wěn)中求新,穩(wěn)中求變[7]》一文中提到過TIOBE index作為世界最知名的編程語言排行榜,卻存在其“不靠譜”的特性,比如這一期排名中,上古時代的編程語言Fortran從去年同期的第24位上升至第11位,僅比PHP落后一位,另一門古老的COBOL語言也從去年同期的第30位上升至第19位,僅僅比大熱的Rust語言落后一位。
因此,對于TIOBE的排名,大家既要了解,也無需過于看重^_^。
言歸正傳,我們來說說Go 1.22版本的變化。
1. 語言變化
Go 1.22對語言語法做了兩處變更,一個是Go 1.21版本[8]中的試驗特性loopvar在Go 1.22中轉(zhuǎn)正落地;另一個也和for循環(huán)有關(guān),那就是for range新增了對整型表達式的支持。兩者相比較,還是第一個變化loopvar帶來的影響更大一些。為什么呢?因此這是Go語言發(fā)展歷史上第一次真正的填語義層面的“坑”,而且修改的是一個在Go源碼中最常用的控制結(jié)構(gòu)的執(zhí)行語義,這很大可能會帶來break change。Go101教程[9]的作者老貘[10]將之成為Go歷史上最大的向后兼容性破壞版本[11]。
注:Go 1.21版本[12]有一個對panic(nil)的語義修正,但我估計很少會有人寫出panic(nil)這樣的代碼。
這次語義修改用一句話表達就是:將經(jīng)典三段式for循環(huán)語句以及for range語句中的用短聲明形式定義的循環(huán)變量從整個循環(huán)定義和共享一個,變?yōu)槊總€迭代定義一個。
這里借用Go官博文章中那個例子再說明一下這個語義變化:
// go1.22-examples/lang/loopvar/main.go
package main
import (
 "fmt"
 "time"
)
func main() {
 done := make(chan bool)
 values := []string{"a", "b", "c"}
 for _, v := range values {
  go func() {
   time.Sleep(time.Second)
   fmt.Println(v)
   done <- true
  }()
 }
 // wait for all goroutines to complete before exiting
 for _ = range values {
  <-done
 }
}我們用Go 1.22.0版本之前的版本,比如Go 1.21.0,來運行該示例:
$go run main.go
c
c
c我們看到:由于v是整個循環(huán)中各個迭代共享的一個變量,所以在每個迭代新創(chuàng)建的goroutine中輸出的v都是循環(huán)結(jié)束后v的最終值c。
如果我們用go 1.22.0來運行上述示例,我們將得到:
// 輸出的值的順序與goroutine調(diào)度有關(guān)
$go run main.go
b
c
a注:關(guān)于Go 1.22版本之前的for range的坑,我的極客時間專欄《Go語言第一課》[13]專欄有圖文并茂的原理講解,歡迎訂閱閱讀。
那么,loopvar這一語義填“坑”究竟會對你的代碼造成怎樣的影響呢?在Russ Cox關(guān)于loopvar語義變更的設(shè)計文檔[14]中提到了:只有g(shù)o.mod中的go version在go 1.22.0及以后的時候才會生效,這是一個漸進式過渡的過程,因此目前無論是開源項目還是商業(yè)項目,只要go.mod中的go version還沒有更新為大于等于go 1.22.0,那么for循環(huán)依然會保留短聲明定義的變量的原語義,這樣這些項目都不會受到影響。
不過,如果是直接在腳本中通過go run xxx.go形式運行某個go源碼的,且當(dāng)前工作目錄以及父目錄下沒有g(shù)o.mod文件的,go 1.22.0會采用新的loopvar語義,這點大家要注意了。
此外,當(dāng)你將go.mod中的go version升級到go 1.22.0或更高版本時,也要注意語義變更可能帶來的問題。在升級go version之前,可以用Go 1.22版本之前的go vet對項目源碼進行一次靜態(tài)分析,對于go vet提示:“l(fā)oop variable v captured by func literal”的地方務(wù)必注意逐個確認。
注:Go 1.22版本中的go vet已經(jīng)移除了在go version >= 1.22.0時,對“l(fā)oop variable v captured by func literal”情況進行警告的功能。
關(guān)于Go 1.22中for range支持后面接整型表達式的“語法糖”新特性以及函數(shù)迭代器的實驗特性[15],這里就不細說了,大家可以看看“Go 1.22新特性前瞻[16]”一文中的說明。
2. 編譯器、運行時與工具鏈
在編譯器、運行時和工具鏈這些方面,Go 1.22的正式版本與“Go 1.22新特性前瞻[17]”一文中使用的Go 1.22rc1版本幾乎沒有差異,這里挑主要內(nèi)容介紹一下,其他一些內(nèi)容可以參考前瞻[18]一文。
Go 1.22版本繼續(xù)在編譯上優(yōu)化PGO(profile-guided optimization), 基于PGO的構(gòu)建可以比以前版本實現(xiàn)更高比例的調(diào)用去虛擬化(devirtualize)。在Go 1.22中,官?給出的PGO帶來的性能提升數(shù)字是2%~14%,這應(yīng)該是基于Google內(nèi)部一些典型的Go程序測算出來的。
注:如果你對PGO優(yōu)化還不是很了解,可以看看“深入理解Profile Guided Optimization(PGO)[19]”這篇文章。
Go 1.22版本編譯器現(xiàn)在可以更多運?devirtualize和inline。在Go編譯器中,devirtualize是一種編譯優(yōu)化技術(shù),旨在消除“虛函數(shù)”調(diào)用的開銷?!疤摵瘮?shù)”是指在面向?qū)ο缶幊讨校ㄟ^基類指針或引用調(diào)用的函數(shù)。在Go中所謂虛函數(shù)調(diào)用指的就是通過接口類型變量進行的方法調(diào)用。由于是動態(tài)調(diào)用,基于接口的方法調(diào)用需要在運行時進行查找和分派,這可能導(dǎo)致一定的性能損失。
而Go編譯器在進行devirtualize優(yōu)化時,會嘗試根據(jù)程序的上下文信息和類型信息,確定方法調(diào)用的具體對象實例。如果編譯器能夠確定調(diào)用的具體實例,則會將通過接口的方法調(diào)用替換為直接調(diào)用具體對象實例的方法,從而消除運行時的開銷,使得通過接口類型變量進行方法調(diào)用的性能得到優(yōu)化提升。
Go 1.22版本中的運行時可以使基于類型的垃圾收集的元數(shù)據(jù)更接近每個堆對象,從而將Go程序的CPU性能(延遲或吞吐量)提高了1-3%。這一變化還支持通過重復(fù)數(shù)據(jù)刪除冗余元數(shù)據(jù),進而將大多數(shù)Go程序的內(nèi)存開銷減少了大約1%。
在工具鏈方面,有三個主要改變這里提一下:
- go work支持vendor
 
在Go 1.22版本中,通過go work vendor可以將workspace中的依賴放到vendor?錄下,同時在構(gòu)建時,如果workspace下有vendor?錄,那么默認的構(gòu)建是go build -mod=vendor,即基于vendor的構(gòu)建。
- go mod init不再care其他vendor工具的配置文件
 
go mod init不再嘗試將其他vendor工具(例如Gopkg.lock )的配置文件導(dǎo)入到go module依賴文件(go.mod)中了,也就是說從Go 1.22版本開始,go module出現(xiàn)之前的那些gopath時代的依賴管理工具正式退出并成為歷史了。
- 改進go test -cover的輸出
 
對于沒有自己的測試文件的包,go test -cover在go 1.22版本之前會輸出:
? mymod/mypack [no test files]但在Go 1.22版本之后,會報告覆蓋率為0.0%:
mymod/mypack coverage: 0.0% of statements3. 標準庫
這里列舉一下標準庫值得關(guān)注的重大變化,大家可以與前瞻[20]一文相互參考著閱讀。
3.1 math/rand/v2:標準庫的第一個v2版本包
Go 1.22中新增了math/rand/v2包,這里之所以將它列為Go 1.22版本標準庫的?次重要變化,是因為這是標準庫第一次為某個包建?v2版本,按照Russ Cox的說法,這次math/rand/v2包的創(chuàng)建,算是為標準庫中的其他可能的v2包“探探路”,找找落地路徑。關(guān)于math/rand/v2包相對于原math/rand包的變化有很多,具體可以參考issue 61716[21]中的設(shè)計與討論。
3.2 增強http.ServeMux表達能力
在Go 1.22版本中,http.ServeMux的表達能力得到了大幅提升,從原先只支持靜態(tài)路由,到新版本中支持如下一些特性:
- "/index.html"路由將匹配任何主機和方法的路徑"/index.html";
 - "GET /static/"將匹配路徑以"/static/"開頭的GET請求;
 - "example.com/"可以與任何指向主機為"example.com"的請求匹配;
 - "example.com/{$}"會匹配主機為"example.com"、路徑為"/"的請求,即"example.com/";
 - "/b/{bucket}/o/{objectname...}"匹配第一段為"b"、第三段為"o"的路徑。名稱"bucket"表示第二段,"objectname"表示路徑的其余部分。
 
并且新版ServeMux在路由匹配性能方面也不輸眾多開源http路由框架太多,后續(xù)建立Go web或api類新項目時,可以考慮首選新版ServeMux來進行路由匹配了,減少對外部的一個依賴。
關(guān)于新版http.ServeMux的具體使用方法,其作者Jonathan Amsterdam(也是log/slog[22]的作者)在官博發(fā)表了一篇名為“Routing Enhancements for Go 1.22[23]”的文章,大家可以詳細參考。
關(guān)于標準庫的其他一些變化,大家可以參考前瞻[24]一文以及更詳細的Go 1.22的發(fā)布說明文檔[25]。
4. 小結(jié)
綜上,Go 1.22版本對語言、編譯器、工具鏈、運行時和標準庫都有一定程度的改進和創(chuàng)新,遺留代碼通過Go 1.22版本的重新編譯便可以得到一定程度的性能上的自然提升,這也體現(xiàn)了Go語言在穩(wěn)中求新、穩(wěn)中求變[26]的特點。
不過這里還要提醒各位Go開發(fā)者,在升級Go 1.22版本時務(wù)必注意潛在的向后兼容性問題,尤其是loopvar語義帶來的變化影響。
本文涉及的源碼可以在這里[27]下載。
參考資料
[1]
Eli Bendersky: https://github.com/eliben
[2] Go 1.22 is released!: https://go.dev/blog/go1.22
[3] Go playground: https://go.dev/play/
[4] Go 1.22新特性前瞻: https://tonybai.com/2023/12/25/go-1-22-foresight/
[5] 《Go語言第一課專欄》: http://gk.link/a/10AVZ
[6] 《和你一起迎接Go的黃金十年》: https://time.geekbang.org/column/article/486536
[7] 2023年Go語言盤點:穩(wěn)中求新,穩(wěn)中求變: https://tonybai.com/2023/12/31/the-2023-review-of-go-programming-language/
[8] Go 1.21版本: https://tonybai.com/2023/08/20/some-changes-in-go-1-21/
[9] Go101教程: https://go101.org/
[10] 老貘: https://github.com/go101
[11] Go歷史上最大的向后兼容性破壞版本: https://twitter.com/go100and1/status/1755598141351677983
[12] Go 1.21版本: https://tonybai.com/2023/08/20/some-changes-in-go-1-21/
[13] 《Go語言第一課》: http://gk.link/a/10AVZ
[14] 關(guān)于loopvar語義變更的設(shè)計文檔: https://go.googlesource.com/proposal/+/master/design/60078-loopvar.md
[15] 函數(shù)迭代器的實驗特性: https://go.dev/wiki/RangefuncExperiment
[16] Go 1.22新特性前瞻: https://tonybai.com/2023/12/25/go-1-22-foresight/
[17] Go 1.22新特性前瞻: https://tonybai.com/2023/12/25/go-1-22-foresight/
[18] 前瞻: https://tonybai.com/2023/12/25/go-1-22-foresight
[19] 深入理解Profile Guided Optimization(PGO): https://andrewwphillips.github.io/blog/pgo.html
[20] 前瞻: https://tonybai.com/2023/12/25/go-1-22-foresight
[21] issue 61716: https://go.dev/issue/61716
[22] log/slog: https://tonybai.com/2022/10/30/first-exploration-of-slog
[23] Routing Enhancements for Go 1.22: https://go.dev/blog/routing-enhancements
[24] 前瞻: https://tonybai.com/2023/12/25/go-1-22-foresight
[25] Go 1.22的發(fā)布說明文檔: https://go.dev/doc/go1.22
[26] Go語言在穩(wěn)中求新、穩(wěn)中求變: https://tonybai.com/2023/12/31/the-2023-review-of-go-programming-language
[27] 這里: https://github.com/bigwhite/experiments/tree/master/go1.22-examples
[28] Gopher部落知識星球: https://public.zsxq.com/groups/51284458844544
[29] 鏈接地址: https://m.do.co/c/bff6eed92687















 
 
 













 
 
 
 