Go版本更新录(202x)
2024-02-21 14:32:12 阿炯

本文是从Go的产品主页分离出来的,专门用于该软件的更新记录,截止到2029年12月31日。

最新版本:1.5
和很多主流语言一样,Go语言编译器最初都是由C语言和汇编语言实现的。C语言和汇编实现的Go编译器(记作A)用来编译Go源文件。那么是否可以用Go语言自身实现一个Go编译器B,用编译器A来编译Go编译器B工程的源码并链接成最终的Go编译器B呢?这就是Go核心团队在Go 1.5版本时做的事情。

他们将绝大多数原来用C和汇编编写的Go编译器以及运行时实现改为使用Go语言编写,并用Go 1.4.x编译器(C与汇编实现的,相当于A)编译出Go 1.5编译器。这样自Go 1.5版本开始,Go编译器就是用Go语言实现的了,这就是所谓的自举。即用要编译的目标编程 语言(Go语言)编写其(Go)编译器。

这之后,Go核心团队基本就告别C代码了,可以专心写Go代码了。这可以让Go核心团队积累更为丰富的Go语言编码经验;同时Go语言自身就是站在C语言等的肩膀上,修正了C语言等的缺陷并加入创新机制而形成的,用Go编码效率高,还可 避面C语言的很多坑。 在这个基础上,使用Go语言实现编译器和runtime还利于Go编译器以及运行时的优化,Go 1.5及后续版本GC延迟大幅降低以及性能的大幅提升都说明了这一点。这就是自举的重要之处。

最新版本:1.6
该版本在语言本身层面并没有任何变化,主要改进包括:
默认使用 cgo 外部 C 编译器
支持 HTTP/2 协议
增加对 64 位 MIPS 的体验支持(Linux)
增加对 32 位 x86 上的 Android 的体验支持
在 FreeBSD 上 go 1.6 默认使用 clang 而不是 gcc 作为外部 C 编译器
在 64 位 PowerPC 上 支持 cgo
NaCI 上 GO 1.5 要求 SDK 版本是 pepper-41,而 Go 1.6 可以使用最新的 SDK 版本
在 32 位 x86 系统中使用 --dynlink 或者 --shared 编译模式,寄存器 CX 被特定内存引用覆盖,可通过编写汇编指令来更改这个行为。
Go 1.6 的改进非常多,详细介绍请看此处

Go1.13 数字表示法
二进制整数:前缀 0b 或 0B 表示二进制整数文字,例如 0b1011。
八进制整数:前缀 0o 或 0O 表示八进制整数文字,例如 0o660。 由前导 0 后跟八进制数字表示的现有八进制表示法仍然有效。
十六进制浮点值:前缀 0x 或 0X 现在可用于表示十六进制格式的浮点数的尾数,例如 0x1.0p-1021。 十六进制浮点数必须始终有一个指数,写为字母 p 或 P 后跟一个十进制指数。
虚数文字:虚数后缀 i 现在可以与任何(二进制、十进制、十六进制)整数或浮点数文字一起使用。
数字分隔符:现在可以使用下划线分隔(分组)任何数字文字的数字,例如 1_000_000、0b_1010_0110 或 3.1415_9265。 下划线可以出现在任何两个数字之间或文字前缀和第一个数字之间。
Go 1.13 移除了计数必须是无符号的限制。此更改消除了许多在使用<< 和 >> 运算符时需要额外转换成uint的必要。

Go1.14 允许嵌入具有重叠方法集的接口
这个迭代可以简单说是实现了方法的重写(C++/Java中的说法)能力。

Go1.15 增加包time/tzdata
1.15 包含一个新包 time/tzdata,它允许将时区数据库嵌入到程序中。导入此包(如 import _ "time/tzdata")允许程序查找时区信息,即使时区数据库在本地系统上不可用。还可以通过使用 -tags timetzdata 构建来嵌入时区数据库。

Go1.16 支持二进制嵌入文件
go 命令现在支持使用新的 //go:embed 指令将静态文件和文件树作为最终可执行文件的一部分。它可以将文件嵌入 Go 代码中,从而方便地在程序中访问这些文件。

Go1.17 允许切片转为数组指针
从切片到数组指针的转换:[]T 类型的表达式 s 现在可以转换为数组指针类型 *[N]T, 假设 a 是转换的结果,如果 len(s) 小于 N。则在范围内的相应索引会引用到相同的底层元素:for 0 <= i < N &a[i] == &s[i] 。如果切片的长度小于数组的长度,就会发生运行时panic。

事实证明在切片元素增加后,对转换后的元素无影响。在开发中,如果我们是切片类型数据,在调用函数需要使用固定长度的数组或数组指针,可以使用这个特性进行转换,以避免在转换过程中发生数据复制,从而提高了程序的性能。此外,将切片转换为数组或数组指针还可以使代码更加简洁和易于理解。1.17版本语言特性除了以上所述,还有其他几点:
unsafe.Add:unsafe.Add(ptr, len) 将 len 添加到 ptr 并返回更新后的指针 unsafe.Pointer(uintptr(ptr) + uintptr(len))。
unsafe.Slice:对于 *T 类型的表达式 ptr,unsafe.Slice(ptr, len) 返回一个 []T 类型的切片,其底层数组从 ptr 开始,长度和容量为 len。

最新版本:1.18
距离 Go 1.17 发布七个月后,Go 1.18 正式于2022年3月中旬正式发布,1.18 是一个包含大量新功能的版本,包括对语言本身做了有史以来最大的改变(泛型)、工具链的实现、运行时和库的更改,还改善了性能。与往常一样,该版本保持了 Go 1的兼容性承诺:几乎所有 Go 程序都能像以前一样继续编译和运行。下面来看一下新版本的一些重大特性:

泛型
以下是关于 Go 1.18 泛型的最明显变化的列表,如需更全面的概述请参阅泛型提案,更详细信息请参阅语言规范
函数和类型声明的语法,现在接受类型参数。
参数化函数和类型可以通过在方括号中列出类型参数来实例化。
新标记~已添加到操作符和标点符号中。
接口类型的语法现在允许嵌入任意类型(不仅仅是接口的类型名称)以及 union 和 ~T 类型元素。,这样的接口只能用作类型约束。
新的预声明标识符 any是空接口的别名,可以用来代替 interface{}。
新的预声明标识符 comparable是一个接口,表示可以使用==或者 != 比较的所有类型的集合,它只能用作(或嵌入)类型约束。

有三个使用泛型的实验包可能有用,这些包在 x/exp 存储库中;但它们的 API 不在 Go 1 兼容性承诺的保证范围内:
golang.org/x/exp/constraints:对通用代码有用的约束,例如 constraints.Ordered.
golang.org/x/exp/slices:对任何元素类型的切片进行操作的通用函数集合。
golang.org/x/exp/maps:对任何键或元素类型的映射进行操作的通用函数集合。

当前的泛型实现具有以下已知限制:
Go 编译器无法处理泛型函数或方法中的类型声明,计划在 Go 1.19 中取消这个限制。
Go 编译器不接受具有预声明函数 real、imag 和 complex 的参数类型的参数,计划在 Go 1.19 中取消这个限制。
如果 m 由 P 的约束接口显式声明,Go 编译器仅支持在类型参数类型 P 的值 x 上调用方法 m。 类似地,方法值 x.m 和方法表达式 P.m 也仅在 m 由 P 显式声明时才受支持,即使 m 可能在 P 的方法集中,因为 P 中的所有类型都实现了 m,计划在 Go 1.19 中取消这个限制。
Go 编译器不支持访问结构字段 x.f,其中 x 是类型参数类型,即使类型参数的类型集中的所有类型都具有字段 f,计划在 Go 1.19 中取消这个限制。
不允许将类型参数或指向类型参数的指针作为结构类型中的未命名字段嵌入,同样地,也不允许在接口类型中嵌入类型参数。
具有多个 term 的 union 元素可能不包含具有非空方法集的接口类型。

泛型代表 Go 生态系统的巨大变化,虽然官方更新了几个支持泛型的核心工具,但还有很多工作要做。剩余的工具、文档和库需要一些时间才能赶上这些语言变化。此外在官方公告中还有这么一段话:可能会有一些使用泛型的代码可以在 1.18 版本中使用,但在以后的版本中会中断。不计划或期望做出任何此类更改,但由于我们今天无法预见的原因,可能需要在未来版本中破坏 1.18 的程序。鼓励在有意义的地方使用泛型,但在生产环境中部署泛型代码时,请谨慎行事(虽然泛型是搞出来了,但很可能有 Bug,不建议在生产中使用)。

模糊测试:Go 1.18 包括 fuzzing(模糊测试) 的实现,如 fuzzing 提案所述,详情请参阅 fuzzing 教程以开始使用。注意,模糊测试会消耗大量内存,并且可能会影响机器运行时的性能。另请注意,模糊引擎在运行时会将扩展测试覆盖率的值写入模糊缓存目录 $GOCACHE/fuzz。目前对可以写入模糊缓存的文件数量或总字节数没有限制,因此可能会占用大量存储空间(可能为 GB 级别)。

编译器:现在编译器可以内联包含范围循环或标记为循环的函数。类型检查器被完全替换以支持泛型,一些错误消息可能使用与以前不同的措辞(提供更多详细信息,或以更有用的方式表述)。由于与支持泛型相关的编译器的更改,Go 1.18 的编译速度可能比 Go 1.17 的编译速度慢大约 15%,代码的执行时间不受影响,目前计划在 Go 1.19 中提高编译器的速度。

错误修正:Go 1.18 编译器可以正确地报告在函数文本中设置但从未使用过的变量的错误(已声明但未使用),解决了一个老问题 issue #8560 。现在在将如 '1' << 32 之类的符文常量表达式作为参数传递给预声明函数 print 和 println 时会报告溢出。

Ports
AMD64:Go 1.18 引入了新的GOAMD64环境变量,它在编译时选择 AMD64 架构的最低目标版本,允许的值为v1、 v2、v3或v4,默认是v1。
RISC-V:Linux 上的 64 位 RISC-V 架构(linux/riscv64 端口)现在支持 c-archive 和 c-shared 构建模式。
Linux:Go 1.18 需要 Linux 内核版本 2.6.32 或更高版本。
Windows:windows/arm 和 windows/arm64 端在支持非合作抢占,有希望解决在调用 Win32 函数时遇到的一些细微的 bug,这些bug在很长一段时间内会阻塞。
iOS:在 iOS(ios/arm64 端口)和在基于 AMD64 的 macOS(ios/amd64 端口)上运行的 iOS 模拟器上,Go 1.18 现在需要 iOS 12 或更高版本; 已停止支持以前的版本。
FreeBSD:Go 1.18 是支持 FreeBSD 11.x 的最后一个版本,Go 1.19 需要 FreeBSD 12.2+ 或 FreeBSD 13.0+。

性能提升:由于 Go1.17 中寄存器 ABI 调用约定扩展到了 RM64 / Apple M1 / PowerPC 64 架构,因此 Go1.18 对这几个架构包含了高达 20% 的 CPU 性能提升。

Go 1.18 版本还包含其他大量更新项,完整更新列表请在发行公告中查看。

Go1.19 内存模型和atomic包
Go 的内存模型现在明确定义了 sync/atomic 包的行为。 happens-before 关系的正式定义已经过修改,以与 C、C++、Java、JavaScript、Rust 和 Swift 使用的内存模型保持一致。现有程序不受影响,随着内存模型的更新,sync/atomic 包中有新的类型,例如 atomic.Int64 和 atomic.Pointer[T],可以更轻松地使用原子值。

新的atomic类型
sync/atomic 包定义了新的原子类型 Bool、Int32、Int64、Uint32、Uint64、Uintptr 和 Pointer。这些类型隐藏了底层值,因此所有访问都被迫使用原子 API。Pointer 还避免了在调用站点转换为 unsafe.Pointer 的需要。Int64 和 Uint64 自动对齐到结构和分配数据中的 64 位边界,即使在 32 位系统上也是如此。1.19版本在语言特性上变更比较少,更多的是在内存模型和GC上,在内存模型和GC上有很大的优化。

最新版本:1.20
继 Go 1.19 发布6个月之后,Go 1.20 已于2023年2月上旬正式发布,该版本的大部分更改都集中在在工具链、运行时和库的实现中。1.20 是最后一个可以在任何版本的 Windows 7、8、Server 2008 和 Server 2012 上运行的版本,Go 1.21 将至少需要 Windows 10 或 Server 2016。且 Go 1.20 是最后一个支持在 macOS 10.13 High Sierra 或 10.14 Mojave 上运行的版本,Go 1.21 将需要 macOS 10.15 Catalina 或更高版本。此外,Go 1.20 还添加了对 RISC-V 上的 FreeBSD (GOOS=freebsd, GOARCH=riscv64) 的实验性支持。在语法方面包含了 4 项变化:
1.Go 1.7 增加了从 slice (切片)到数组指针转换的功能,Go 1.20 对该功能进行了扩展 —— 可直接从 slice 转换成数组。比如给定一个 slicex,[4]byte(x) 可以写成 *(*[4]byte)(x)。
2.标准库 unsafe 包定义了 3 个新函数:SliceData,String 和 StringData。与 Go 1.17 的 Slice 一起,这些函数现在提供了构建和解构 slice 和字符串值的完整功能,而不依赖于它们的精确表示。
3.Go 语言规范进行了更新,定义结构体变量的值每次只比较一个字段,字段比较的顺序和字段在结构体里定义的顺序保持一致。一旦某个字段的值比较出现不一致,就会马上停止比较。
4.Comparable 类型(例如普通接口 ordinary interfaces)现在可以满足 comparable 约束,即便类型实参 (type argument) 不是严格可比较类型。

一些其他更新内容还包括:
$GOROOT/pkg 目录不再存储标准存档的预编译包存档,以减少 Go 发行版的大小。
go 命令现在定义架构功能 build flags,如 amd64.V2,以允许根据特定 CPU 架构功能的存在或不存在来选择包实现文件。这对于 x86_64 微架构特性级别的工作来说是个好消息。
go build 和 go install 以及其他与构建相关的命令现在支持 “-cover” flag,用于启用带有 code coverage instrumentation 的构建。
由于在垃圾收集器上的工作,内存开销减少和 CPU 性能提高高达 2%。
对 Profile Guided Optimizations (PGO) 的预览支持。
在 Linux 上,linker 现在在链接时为 glibc 或 musl 选择动态解释器。
一个新的 crypto/ecdh 包,为 NIST curves 和 Curve25519 上的 Elliptic Curve Diffie-Hellman 密钥交换提供明确支持。

更多详情可查看官方公告。1.20版本在语言特性上变更也比较少,更多的是在编译器和链接器的优化。Go 1.18 和 1.19 的构建速度有所下降,这主要是由于增加了对泛型的支持和后续工作。1.20 将构建速度提高了 10%,使其与 Go 1.17 保持一致;相对于 Go 1.19,生成的代码性能也普遍略有提升。

最新版本:1.21
Go 1.21版本大部分变化集中在工具链、运行时和库的实现。该版本保持了 Go 1 的兼容性承诺并有所改进。开发团队希望几乎所有 Go 程序都能像以前一样继续编译和运行。此外对版本编号进行了一个小更改。过去使用 Go 1.N 来指代整个 Go 语言版本和版本系列,以及该系列中的第一个版本。从 Go 1.21 开始,第一个版本现在是 Go 1.N.0。2023年8月上旬发布的是 Go 1.21 语言及其初始实现 Go 1.21.0 release。像 go version 这样的工具会将版本号显示为 "go1.21.0"。有关新版本编号的详细信息,请参阅"Go Toolchains" 文档中的 "Go versions"。

改进工具链
配置文件引导优化 (PGO) 功能正式 GA。在 1.20 中处于预览阶段的启用配置文件引导优化 (PGO) 功能现已正式 GA。如果主软件包目录中存在名为 default.pgo 的文件,go 命令将使用它来启用 PGO 构建。Profile-guided optimization (PGO) 是计算机编程中的一种编译器优化技术,翻译过来是使用配置文件引导的优化。PGO 也被称为:
Profile-directed feedback (PDF)
Feedback-directed optimization (FDO)

其原理是编译器使用程序的运行时 profiling 信息,生成更高质量的代码,从而提高程序的性能。PGO 作为一项通用的编译器优化技术,不局限于某一门语言。比如 Rust 编译器也在探索使用 PGO,微软则采用 LTO+PGO 来优化 Linux 内核。在 Go 语言中,最初关于 PGO 的提案是建议向 Go GC 工具链增加对配置文件引导优化 (PGO) 的支持,以便工具链能根据运行时信息执行特定于应用程序和工作负载的优化。开发团队测试了 PGO 对多种 Go 程序的影响,发现性能提高了 2-7%。更多详细信息查看 PGO 文档。

语言变更
添加新的内置函数:min, max 和 clear
对泛型函数的类型推断进行了多项改进,包括扩展和解释清楚规范中类型推断的描述
在未来版本中,开发团队计划解决 Go 编程中最常见的问题之一:循环变量捕获 (loop variable capture)。1.21 附带了此功能的预览版,目前可以使用环境变量在代码中启用该功能。

添加新的标准库
log/slog 包:用于结构化日志记录
slices 包:增用于对任何元素类型的切片进行常见操作,这个包比 sort 包更快、更符合人体工程学
maps 包:用于对任何类型 key-value 进行常见操作
cmp 包:用于比较有序值 (ordered values)

优化性能
除了启用 PGO 时的性能改进之外:Go 编译器本身已经在 1.21 中启用 PGO 进行了重建,因此它构建 Go 程序的速度提高了 2-4%,具体取决于主机架构
由于垃圾收集器的调整,某些应用程序的尾部延迟可能会减少高达 40%
现在使用 runtime/trace 收集跟踪在 amd64 和 arm64 上产生的 CPU 开销要小得多

支持 WASI
Go 1.21 已实验性支持 WebAssembly System Interface (WASI), Preview 1 (GOOS=wasip1,GOARCH=wasm)。为了方便编写更通用的 WebAssembly (WASM) 代码,编译器还支持从 WASM 主机导入函数的新指令:go:wasmimport。

更多详情查看发行说明

最新版本:1.22
1.22于2024年2月发布。

语言变化
长期存在的 "for" 循环在迭代之间意外共享循环变量的问题现已得到解决。从 1.22 开始,以下代码将按一定顺序打印 "a"、"b" 和 "c":
func main() {
    done := make(chan bool)
    values := []string{"a", "b", "c"}
    for _, v := range values {
        go func() {
            fmt.Println(v)
            done <- true
        }()
    }
    // wait for all goroutines to complete before exiting
    for _ = range values {
        <-done
    }
}

有关这一变更以及有助于防止代码意外中断的工具的更多信息,请参阅之前的《Fixing For Loops in Go 1.22》。第二个语言变化是支持整数范围:
package main
import "fmt"
func main() {
    for i := range 10 {
        fmt.Println(10 - i)
    }
    fmt.Println("go1.22 has lift-off!")
}

第二个语言变化是支持整数范围:a 在这个倒计时程序中,i 的取值范围为 0 至 9(含 9)。

性能提高
Go 运行时中的内存优化可将 CPU 性能提高 1-3%,同时还可将大多数 Go 程序的内存开销减少约 1%。在1.21 中,我们为 Go 编译器提供了配置文件引导优化 (PGO),而且这一功能还在不断改进。1.22 中新增的优化之一是改进了虚拟化,允许静态调度更多的接口方法调用。启用 PGO 后,大多数程序的性能将提高 2% 至 14%。

标准库变动
新的 math/rand/v2 软件包提供了更简洁、更一致的应用程序接口,并使用了质量更高、速度更快的伪随机生成算法。
net/http.ServeMux 使用的模式现在可接受方法和通配符。例如:路由器接受 GET /task/{id}/ 这样的模式,该模式只匹配 GET 请求,并在一个可通过 Request 值访问的映射中捕获 {id} 段的值。
database/sql 包中新增了 Null [T] 类型,为扫描可为空的列提供了一种方法。
在 slices 包中添加了 Concat 函数,用于连接任意类型的多个片段。