语言(Language)
slice转数组
总结:slice转为数组时,会把slice底层数组的值拷贝一份出来。转换后得到的数组的地址空间和slice底层数组空间不一样。
Comparable类型
Go 1.20之前的版本认为空接口类型interface{}并没有实现comparable类型约束,不能作为类型实参传给类型参数。
Go 1.20版本开始,不会编译报错,因为interface类型是comparable type
unsafe包
Go 1.17版本在unsafe package里引入了Slice函数,如下所示:
在Go 1.20版本里,标准库unsafe package定义了3个新的函数:
有了这4个函数,可以构造和解构slice和string。
可移植性(Ports)
Darwin and iOS
Go 1.20将会成为支持macOS 10.13 High Sierra和10.14 Mojave的最后一个版本。
如果未来想在mac电脑上使用Go 1.21或者更新的Go版本,只能用macOS 10.15 Catalina和更新的macOS版本。
FreeBSD/RISC-V
Go 1.20增加了对于RISC-V架构在FreeBSD操作系统的实验性支持。
工具链(Go Tools)
$GOROOT/pkg
路径不再存储标准库源代码编译后生成的文件,包括以下几点:go install
不再往$GOROOT/pkg
目录写文件。
go build
不再检查$GOROOT/pkg
下的文件。
- Go发布包不再带有这些编译文件。
从Go 1.20开始,标准库的package会按需编译,编译后生成的文件会缓存在编译缓存(build cache)里,就像非
GOROOT
下的package一样。这个修改减小了Go安装包的大小。go test -json
的实现在Go 1.20版本更加鲁棒,对使用go test -json
的开发者来说,不用做任何改变。但是,直接调用Go工具
test2json
的开发者,需要给测试的可执行程序增加-v=test2json
参数,例如go test -v=test2json
或者./pkg.test -test.v=test2json
,而不是仅仅加一个-v
标记。go test -json
的另外一个修改是在每个测试程序执行的开始,增加了一个Action
事件。当使用go命令同时运行多个测试程序时,这些Action
事件的执行会按照命令行里的package的顺序按序执行。go
命令现在新增了和CPU架构相关的编译标记参数,例如amd64.v2
。有了这个参数后,在业务代码实现的时候就可以根据CPU架构的不同而做不同的处理。更多细节可以参考:go help buildconstraint
([1]) 。go
子命令现在支持-C <dir>
参数,可以在执行命令前改变目录到<dir>
下。如果一个脚本要在多个不同的Go module下执行,这个特性会带来方便。go
build
和 go
test
命令不再支持-i
参数,这个参数从 Go 1.16版本开始弃用([2])。go generate
命令接受 -skip <pattern>
参数,可以跳过匹配 <pattern>
格式的 //go:generate
指令。go test
命令接受-skip <pattern>
参数,可以跳过匹配 <pattern>
格式的测试用例。go build
, go install
和其它编译相关的命令新增了一个-pgo
标记参数,可以辅助开发者做程序优化。-pgo
指定的是profile文件的路径。如果-pgo=auto
,那go命令会在main这个包的路径下去找名为default.pgo
的文件。-pgo=off
可以关闭优化。详情可以参考:PGO Proposal([3])。go build
, go install
和其他编译相关的命令新增了一个-cover
标记参数,可以用来对编译出来的可执行程序做代码覆盖率收集,详情可以参考本文后面介绍。go version
go version -m
命令支持读取和解析更多类型的Go二进制文件。比如通过
go build -buildmode=c-share
编译出来的Windows DLL文件以及没有可执行权限的Linux二进制文件,现在都可以被go version -m
解析和识别到。Cgo
早期Go语言的一些标准库是用C语言实现的,需要依赖cgo来作为go语言和C语言的桥梁。
现在Go语言开始去除对C语言的的依赖。
从Go 1.20版本开始,如果机器上没有C语言的工具链,go命令会默认禁用
cgo
。具体而言:如果没有设置
CGO_ENABLED
和CC
环境变量,而且默认的C语言编译器(例如clang
和gcc
)也找不到,那CGO_ENABLED
会默认为0。当然开发者可以通过设置CGO_ENABLED
环境变量的值来改变CGO_ENABLED
的值。这个修改会让Go语言减少对C语言工具链的依赖,适配更多的环境,尤其是最小化的容器环境以及macOS环境。
Go标准库里使用了cgo的package有:
net
([4]), os/user
([5]) 和 plugin
([6])。在macOS环境,
net
和os/user
包已经被重写了,不再依赖cgo。现在net
和os/user
的代码实现既可以用cgo编译,也可以不用cgo编译。同时,在macOS环境,race detector已经被重写了,不再依赖cgo。在Windows环境,
net
和os/user
没有使用过cgo。在其它操作系统上,如果编译的时候禁用了cgo,那会使用这些包的纯go语言实现。
在macOS环境,race detector已经被重写了,不再依赖cgo。
Cover(代码覆盖率检测)
Go 1.20版本之前只支持对单元测试(unit test)收集代码覆盖率,从Go 1.20版本开始支持对任何Go程序做代码覆盖率收集。
那如何收集呢?需要做如下操作:
- 给
go build
编译命令增加cover
标记
- 给环境变量
GOCOVERDIR
赋值为某个路径
- 运行
go build
编译出来的可执行程序时,会把代码覆盖率文件输出到GOCOVERDIR
指定的路径下。
Vet
检测循环变量被嵌套子函数错误使用的场景
这个本质上和goroutine与闭包函数一起使用时,遇到的循环变量问题一样。
Go 1.20版本开始通过
go vet
可以检测出单元测试里的这类问题。检查错误时间格式
对于
Time.Format
([10]) 和time.Parse
([11]),如果代码里要转成yyyy-dd-mm的格式,会给出提示。因为yyyy-dd-mm不符合常用的日期格式标准,ISO 8601日期格式是yyyy-mm-dd格式。
运行时(Runtime)
Go 1.20版本的运行时新增了arena内存分配这个新功能的实验性支持,可以让Go程序释放更多内存空间,节省内存占用。
想了解什么是arena内存分配的,可以参考:https://github.com/golang/go/issues/51317。
如果area内存分配使用恰当,对于需要频繁内存分配的应用,可以提升多达15%的CPU性能。
使用方式为编译Go程序时,添加
GOEXPERIMENT=arenas
参数。代码里如果有import arena
,也需要添加这个编译参数。此外,垃圾回收器的一些内部数据结构的设计做了优化,在时间和空间上更高效,可以节省内存开销,提升2%左右的CPU总体性能。
Go 1.20还新增了一个
runtime/coverage
包,调用这个包的API可以把程序运行的代码覆盖率数据输出到指定文件。编译器(Compiler)
Go 1.20新增了PGO(profile-guided optimization)特性,可以帮助开发者做程序性能优化。
目前,编译器支持pprof CPU profile,这种类型的profile可以通过例如
runtime/pprof
或net/http/pprof
收集得到。如果要开启PGO,在使用
go build
编译程序的时候,要增加-pgo
参数。-pgo
指定的是profile文件的路径。如果-pgo=auto
,那go命令会在main这个包的路径下去找名为default.pgo
的文件。-pgo=off
可以关闭优化。详情可以参考:PGO Proposal([1])。如果使用了PGO,编译器会对被调用比较多的函数,更多地使用inline function的方式去做性能优化。
性能测试表明,如果开启了profile-guided inlining optimization,可以提升3%-4%的性能,后期Go会加入更多的PGO优化支持。
注意,由于PGO并不是稳定版本,生产环境使用需要小心。
汇编器(Assembler)
此外,从Go 1.20开始,编译器禁止匿名interface嵌套,如下代码会编译失败。
链接器(Linker)
在Linux环境,链接器会在进行link操作的时候,为
glibc
或者musl
选择动态解释器。在Windows环境,链接器现在支持基于LLVM的C语言工具链。
Go 1.20版本开始,使用
go:
和type:
作为前缀,用于编译器生成的符号,而抛弃使用go.
和type.
作为前缀。这可以避免歧义,因为有的go package的命名是以
go.
开始的。Bootstrap(自举)
如果要编译Go语言本身的源代码,必须要设置
GOROOT_BOOTSTRAP
环境变量。在 1.4 版本开始,Go语言实现了自举,即可以通过1.4版本来编译安装之后版本的编译器。
假设
GOROOT_BOOTSTRAP
环境变量没有设置,那Go 1.17版本及之前的版本,会尝试在 $HOME/go1.4
(%HOMEDRIVE%%HOMEPATH%\go1.4
on Windows)寻找Go 1.4或者更新的bootstrap工具链。Go 1.18和Go 1.19首先会找
$HOME/go1.17
或 $HOME/sdk/go1.17
,找不到的话,就回退去找 $HOME/go1.4
。Go 1.20要实现自举,需要依赖Go 1.17的最新子版本,即Go 1.17.13版本。
Go 1.20会先找
$HOME/go1.17.13
或 $HOME/sdk/go1.17.13
,如果找不到,就回退找$HOME/go1.4
。在接下来,Go官方会把自举的工具链每年向前推进一步。预期是Go 1.22会要求依赖Go 1.20的最新子版本用于Go 1.22的自举。
核心库(Core library)
crypto/ecdh
Go 1.20新增了
crypto/ecdh
这个package,ecdh
实现了Elliptic Curve Diffie-Hellman这个新的加密算法。封装多个error
Go 1.20允许一个error变量里封装多个error。
这段程序的输出结果为:
fmt.Errorf
里带有%w
参数,就会返回一个实现了Unwrap方法的error类型的变量。HTTP ResponseController
net/http
这个package新增了名为ResponseController
的新类型。A ResponseController is used by an HTTP handler to control the response.
A ResponseController may not be used after the Handler.ServeHTTP method has returned.
详情可以参考:https://pkg.go.dev/net/http@master#ResponseController。
Rewrite钩子函数
httputil.ReverseProxy
类型新增了一个 Rewrite
方法,这是一个钩子函数,用来取代之前的Director
钩子函数。详情可以参考:https://pkg.go.dev/net/http/httputil@master#ReverseProxy.Rewrite。
标准库的修改
- bytes
- 新增了
CutPrefix
和CutSuffix
函数,这2个函数功能上类似TrimPrefix
和TrimSuffix
,但是还会返回一个bool类型的变量,表示这个string是否被修改了。 - 新增了
Clone
函数,会创建一个byte slice的拷贝。(深拷贝)
- encoding/binary
ReadVarint
和ReadUvarint
函数如果读的数据的值被损坏,比如只写了一部分内容,会返回io.ErrUnexpectedEOF
,而不是像之前返回io.EOF
。
- errors
- 新的
Join
函数可以把多个error变量的值组合在一起,封装为一个新的error变量。
- fmt
Errorf
支持%w
格式化字符串,可以返回一个实现了Unwrap方法的error类型变量。
- strings
- 新增了
CutPrefix
和CutSuffix
函数,这2个函数功能上类似TrimPrefix
和TrimSuffix
,但是还会返回一个bool类型的变量,表示这个string是否被修改了。 - 新增了
Clone
函数,会创建一个string的拷贝。
- sync
Map
类型新增了3个新方法:Swap
,CompareAndSwap
和CompareAndDelete
,允许对已有的map做原子更新。
- testing
- 新增了
B.Elapsed
方法,可以返回当前的benchmark性能测试耗时了多久。
- time
- 新增了3个常量
DateTime
,DateOnly
和TimeOnly
,方便开发者做格式转换,不用在代码里写死"2006-01-02 15:04:05"。