上周Golang官方发布了,其最新稳定版本1.21,该版本在在PGO功能、语言层面、标准库等方面均有更新。另外提供了新的完善的兼容性机制,比如GODEBUG、go.mod规则以及工具链管理来实现向前(1.x)向后兼容问题(2.x),以此来作为后续向Golang 2版本过渡做准备。
目前官方已经提供了各个平台的安装包,大家可以对应平台下载对应的版本。
在1.20中发布的预览功能文件引导优化(PGO)功能,在1.21中正式启用。项目主包中存在efault.pgo中,go命令将会自动使用它来启用PGO构建。根据官方基准测试PGO 可以改善程序的性能,可以提高2-7%不等。而且所有工具都能实现向前和向后语言版本的兼容(关于兼容性问题,后面会讨论)。
在更复杂的情况下,go build命令提供了-pgo标志来控制PGO配置文件选择。 默认情况下,该标志设置为-pgo=auto,它遵循上述行为,使用default.pgo。
go build -pgo= /tmp/foo .pprof
新的内置函数:min, max和clear。
需要说明的是clear函数,其参数为map,slice,或type类型,该删除会删除或清零该类型下所有元素。
var a = [...]int{0, 1, 2, 3, 4, 5, 6, 7}package mainimport ("fmt""math")func main() {a := map[float64]bool{100: true, 1000: true, math.NaN(): true, math.NaN(): true}delete(a, math.NaN())fmt.Printf("before clear, a len: %d\n", len(a))clear(a)fmt.Printf("after clear, a len: %d\n", len(a))}
对泛型函数的类型推断进行了多项改进。
在Go的未来版本中,计划解决最常见的问题之一循环变量捕获问题。
kCopy:=k防范&kCopy用于循环体的末尾。然而,事实证明modelToAuthzPB保留了一个指向几个字段的指针v,在读这个循环时是不可能知道的。该错误的最初影响是Let's Encrypt需要撤销超过300万个不当颁发的证书 。
Go 1.21 提供了此功能的预览版,可以在代码中通过环境变量启用该功能。
用于结构化日志记录的新log/slog包。
用于常见操作的新切片包,在任何元素类型的切片上。包括通常的排序功能包更快、更符合人体工程学,比如,sort 。
新maps包用于地图上常见操作的,包括任何键或元素类型。
新的cmp软件包以及用于比较的新实用程序有序值。
除了启用PGO 时的性能改进之外:
Go 编译器本在1.21中启用PGO进行了重建,并且作为结果,它构建 Go程序的速度提高了2-4%,具体取决于主机架构。
由于GC的调整,某些应用程序可能会看到高达40%尾部潜伏期减少。
收集跟踪使用运行时/跟踪在amd64和arm64上产生的CPU成本大大减小。
Go 1.21 添加了WebAssembly系统接口(WASI),预览版1 ( GOOS=wasip1, GOARCH=wasm)。用于方便编写更通用的WebAssembly (Wasm) 代码,编译器还支持从Wasm主机导入函数的新指令:
go:wasmimport
Go 1.21 扩展并规范了GODEBUG的使用。首先,对于Go 1兼容性允许的任何更改,但仍然可能会破坏现有的程序,新的GODEBUG可以用来测试兼容性方法,设计了更改以保留尽可能多的现有程序尽可能工作。对于其余程序,新方法是:
这样Go的每个新版本都应该是最好的旧版本Go的实现,甚至保留了在编译旧代码时以前的行为,在后续版本中以兼容但破坏的方式进行了更改。
GODEBUG的设置可以通过
go list -f '{{.DefaultGODEBUG}}' my/main/package
来查看。
例如,在 Go 1.21 中,panic(nil)现在会导致(非零)运行时panic,使得结果为recover。用这种机制可靠地报告当前goroutine是否处于panic态。这种新行为由GODEBUG设置控制,因此依赖于在主包的go.mod的go行:如果它说go 1.20或者更早的时候,panic(nil)仍然是允许的。如果它说go 1.21或稍后, anic(nil)变成恐慌 runtime.PanicNilError。并且可以通过在 package main 中添加如下行来显式覆盖基于版本的默认值:
//go:debug panicnil=1
这种功能组合意味着程序可以更新到更新的工具链的同时保留他们使用的早期工具链的行为,可以根据需要对特定设置进行更细粒度的控制,并可以使用生产监控来了解哪些工作在实践中使用这些非默认行为。结合起来,这些应该可以使新工具链的推出变得更加顺利比过去顺利。
前向兼容性是指当Go工具链尝试构建适用于较新版本Go代码。如果程序依赖于模块M并且需要一个bug修复M v1.2.3中添加的问题,可以在go.mod添加require M v1.2.3,保证程序不会针对旧版本的M进行编译。但是如果程序需要特定版本的Go,那么没有任何方式可以表达这一点:特别是go.mod没有表达这一点。
例如,如果编写使用新泛型的代码,可以go.mod文件写go 1.18,但这不会阻止早期版本的Go尝试编译代码,产生如下错误:
cat go.modgo 1.18module examplego versiongo version go1.17go build# example./x.go:2:6: missing function body./x.go:2:7: syntax error: unexpected [, expecting (note: module requires Go 1.18
两个编译器错误是误导性的噪音。真正的问题是由go命令作为提示:程序无法编译,所以go命令点排除潜在的版本不匹配问题。
在这个例子中,很幸运构建失败了。如果编写的代码只能在Go 1.19或更高版本中正确运行,因为它取决于该补丁版本中修复的错误,但没有使用任何Go 1.19 特定的语言功能或代码中的包早期版本的Go将编译它并默默地成功。
从Go 1.21 开始,Go工具链将go.mod不是作为指导方针,而是作为规则,并且该线可以列出特定的点版本或候选版本。也就是说,如果不在go.mod指明版本Go 1.21.0就会无法构建代码。
允许旧版本的Go尝试的主要原因编译较新的代码是为了避免不必要的构建失败。Go版本太低了,真是令人沮丧旧的构建程序,特别是如果它无论如何都可以工作(也许这个要求是不必要的保守),尤其是当更新到较新的Go版本有点困难时。为了减少执行的影响,Go 1.21 还在核心发行版中添加了工具链管理。
当需要新版本的Go模块时,go命令为下载。从 Go 1.21 开始,当需要更新的 Go工具链时,go命令也会下载。这个功能就像Node的nvm或Rus的rustup,但内置于核心go命令而不是一个单独的工具。
如果运行的是Go 1.21.0 并且运行go命令,go build,在一个模块中go.mod说的是go 1.21.1, go 1.21.0 go命令会注意到需要Go 1.21.1,会下载它,然后重新调用该版本的go命令来完成构建。这是该go命令下载并运行这些其他工具链,它不会将它们安装在路径中或覆盖当前安装。相反,他会将它们下载为Go模块,继承所有模块的安全和隐私优势,然后它从模块缓存中运行它们。
还有一个新的toolchain线路输入go.mod指定了在特定模块中工作时使用的最小Go工具链。相比之下go toolchain没有强加要求在其他模块上。 例如, 对一go.mod内容:
module mgo 1.21.0toolchain go1.21.4
表示其他模块需要m至少需要提供Go 1.21.0,但当工作时m本身想要一个更新的工具链,至少Go 1.21.4。
这go和toolchain可以使用更新需求go get就像普通的模块要求一样。例如,如果正在使用一个在 Go 1.21候选版本中,可以开始使用Go 1.21.0在特定模块中运行:
go get go@1.21.0
它会下载并运行Go 1.21.0来更新go,以及未来的调用go命令将看到该行go 1.21.0并自动重新调用该版本。
或者,如果想在模块中开始使用Go 1.21.0,但go行设置为旧版本,以帮助保持兼容性早期版本Go,可以更新toolchain:
go get toolchain@go1.21.0
如果想知道特定的Go版本正在运行模块,可以运行
go version
可以使用以下命令强制使用特定的Go工具链版本GOTOOLCHAIN 环境变量。例如,要使用Go 1.20.4 测试代码:
GOTOOLCHAIN=go1.20.4 go test
最后,形式的GOTOOLCHAIN设置 ersion+auto指 使用version默认情况下,但也允许升级到新版本。如果安装了Go 1.21.0,那么当Go 1.21.1发布时, 可以通过设置默认的GOTOOLCHAIN来更改系统默认值:
go env -w GOTOOLCHAIN=go1.21.1+auto