tutorial,

golang 1.14 1.15 1.16 新特性一览

前言

笔者一直使用 golang 1.13 版本开发项目,随着 golang 的不断更新,新版本的许多特性也很实用,是时候升级一下了。

本文试图整理近三个版本的新特性,希望能帮助到同笔者一样想升级 golang 版本的同学们。

因为需要对比多个版本的不用,所以推荐大家安装下 gvm 方便切换 golang 版本。

注:如果大家想用 gvm 管理不同版本的 go,使用 VSCode 的时候需要手动去设置里面指定路径 go.gopathgo.goroot

Ports

支持 arm64

Go 1.16 添加了对 macOS ARM64 的支持(也称为 Apple 芯片)。

Runtime

defer

Go 1.14 提高了 defer 的性能,几乎是零开销。defer 现在可以在对性能要求很高的代码中使用,而无需担心开销

timer

golang timer 长期以来性能受到诟病Go 1.14 几乎重新实现 了 timertime.Aftertime.Ticknet.Conn.SetDeadline 较少的锁争,更少的上下文切换,性能都得到了巨大提升。

以下为官方的 benchmark 数据

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
runtime: switch to using new timer code
No big changes in the runtime package benchmarks.

Changes in the time package benchmarks:

name                      old time/op  new time/op  delta
AfterFunc-12              1.57ms ± 1%  0.07ms ± 1%  -95.42%  (p=0.000 n=10+8)
After-12                  1.63ms ± 3%  0.11ms ± 1%  -93.54%  (p=0.000 n=9+10)
Stop-12                   78.3µs ± 3%  73.6µs ± 3%   -6.01%  (p=0.000 n=9+10)
SimultaneousAfterFunc-12   138µs ± 1%   111µs ± 1%  -19.57%  (p=0.000 n=10+9)
StartStop-12              28.7µs ± 1%  31.5µs ± 5%   +9.64%  (p=0.000 n=10+7)
Reset-12                  6.78µs ± 1%  4.24µs ± 7%  -37.45%  (p=0.000 n=9+10)
Sleep-12                   183µs ± 1%   125µs ± 1%  -31.67%  (p=0.000 n=10+9)
Ticker-12                 5.40ms ± 2%  0.03ms ± 1%  -99.43%  (p=0.000 n=10+10)
Sub-12                     114ns ± 1%   113ns ± 3%     ~     (p=0.069 n=9+10)
Now-12                    37.2ns ± 1%  36.8ns ± 3%     ~     (p=0.287 n=8+8)
NowUnixNano-12            38.1ns ± 2%  37.4ns ± 3%   -1.87%  (p=0.020 n=10+9)
Format-12                  252ns ± 2%   195ns ± 3%  -22.61%  (p=0.000 n=9+10)
FormatNow-12               234ns ± 1%   177ns ± 2%  -24.34%  (p=0.000 n=10+10)
MarshalJSON-12             320ns ± 2%   250ns ± 0%  -21.94%  (p=0.000 n=8+8)
MarshalText-12             320ns ± 2%   245ns ± 2%  -23.30%  (p=0.000 n=9+10)
Parse-12                   206ns ± 2%   208ns ± 4%     ~     (p=0.084 n=10+10)
ParseDuration-12          89.1ns ± 1%  86.6ns ± 3%   -2.78%  (p=0.000 n=10+10)
Hour-12                   4.43ns ± 2%  4.46ns ± 1%     ~     (p=0.324 n=10+8)
Second-12                 4.47ns ± 1%  4.40ns ± 3%     ~     (p=0.145 n=9+10)
Year-12                   14.6ns ± 1%  14.7ns ± 2%     ~     (p=0.112 n=9+9)
Day-12                    20.1ns ± 3%  20.2ns ± 1%     ~     (p=0.404 n=10+9)

Updates #6239
Updates #27707

Change-Id: I51e25a90f941574f1a9cf83a22e84ac8c678537d
Reviewed-on: https://go-review.googlesource.com/c/go/+/171883
Run-TryBot: Ian Lance Taylor <iant@golang.org>
TryBot-Result: Gobot Gobot <gobot@golang.org>
Reviewed-by: Brad Fitzpatrick <bradfitz@golang.org>
Reviewed-by: Michael Knyszek <mknyszek@google.com>
Reviewed-by: Emmanuel Odeke <emm.odeke@gmail.com>

Goroutine 支持异步抢占

Go 1.12 版本以前,调度器只能依靠 goroutine 主动让出 CPU 资源,这样存在非常严重的调度问题:

设想一下,假如一个 goroutine 陷入死循环,它会一直占用系统资源,会导致调度器延时和垃圾回收延时。

垃圾回收需要暂停整个程序(Stop-the-world,STW),如果没有抢占可能需要等待几分钟的时间,导致整个程序无法工作。

Go 1.12 版本中,采用一个非协作式的抢占技术, 来允许对很长的循环进行抢占。在特定时机插入函数,通过函数调用作为入口触发抢占,实现了协作式的抢占式调度。这种抢占方式并不是强制发生的,不会使一个没有主动放弃执行权、且不参与任何函数调用的 goroutine 被抢占。

Go 1.14 版本中,引入了基于系统信号的异步抢占调度,这样,像上面的无函数调用的死循环 goroutine 也可以被抢占了,不过代价是出现死循环导致的性能下降问题更难排查了

更高效的页分配器(page allocator)

Go 1.14 版本中,页分配器效率变高,并且在 GOMAXPROCS 值较高时,导致的锁争用显着减少。 这是最引人注目的,因为可以并行并以较高的速率完成较大分配的较低延迟和较高吞吐量,能显著的提高 Go 并行能力

提高小对象在高内核数下的分配

Go 1.15 版本中,小对象的分配现在在高内核数下表现更好,并且最坏情况下的等待时间也更短

语言层

允许嵌入具有相同方法的接口

Go 1.14 根据重叠接口建议允许嵌入具有相同方法的接口。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
type A interface {
  TestA()
	String() string
}

type B interface {
  TestB()
  String() string
}

type C interface {
  A
  B
}

在 Go 1.14 以前会报 String redeclared 错误。

注:如果两个接口有相同名称的方法,但是签名不一样的话,还是会报错

不允许相对路径导入

Go 1.16 开始禁止 import 导入的模块以 . 开头,模块路径中也不允许出现任何非 ASCII 字符,所以下面的代码不再合法:

1
2
3
4
5
import (
  "./tools/image"
  "../models/core"
  "some.pkg.com/好用的库/"
)

工具

Module

Go 1.14 可以在 Go 命令中使用 Module 支持,以供生产使用,并且鼓励所有用户迁移到 Go Module 以进行依赖项管理

Go 1.16 默认情况下,启用 Go Module,而不管当前工作目录或父目录中是否存在 go.mod 文件。更准确地说,GO111MODULE 环境变量现在默认为 on。要切换到以前的行为,请设置 GO111MODULE 为 auto。并且发布了开发和发布模块的官方文档

Test

Go 1.14 开始,go test -v 支持流式传输 t.Log 输出,而不是在所有测试结束时输出。

Go 1.14 testing 包的 T、BTB 都加上了 CleanUp 方法。

在并行测试和子测试中,CleanUp(f func()) 非常有用,它将以后进先出的方式执行 f(如果注册多个的话)。

举一个例子:

1
2
3
4
5
6
7
8
9
func TestSomething(t *testing.T) {
	t.CleanUp(func() {
		fmt.Println("Cleaning Up!")
	})

	t.Run(t.Name(), func(t *testing.T) {

	})
}

可以在 test 或者 benchmark 结束后调用 t.CleanUpb.CleanUp 做一些收尾统计工作,非常实用!

Go 1.16 开始,在测试用例里调用 os.Exit(0) 会从程序终止变成测试失败

GOPROXY 支持跳过返回错误的代理

Go 1.15 开始,GOPROXY 环境变量支持跳过返回错误的代理。代理 URL 现在可以用逗号或竖线字符分隔。如果代理 URL 后面带有逗号,则该 go 命令将仅在 404 或 410 HTTP 响应后尝试列表中的下一个代理。如果代理 URL 后面带有竖线字符,则该 go 命令将在出现任何错误后尝试列表中的下一个代理。请注意,默认值 GOPROXY 保持 https://proxy.golang.org,direct,在发生错误的情况下不会回退。

全新链接器

Go 1.15 对链接器的重大改进,可减少链接器资源的使用(时间和内存)并提高代码的健壮性/可维护性

对于一组典型的大型 Go 程序,对于 ELF 在 amd64 体系结构上运行的基于 OS(Linux,FreeBSD,NetBSD,OpenBSD,Dragonfly 和 Solaris)的操作系统,链接速度提高 20%,平均所需内存减少 30%,对于其他架构/操作系统组合。

改进链接程序性能的关键因素是新设计的目标文件格式,以及内部阶段的改进以提高并发性(例如,将重定位并行应用于符号)。Go 1.15 中的目标文件比其 1.14 等价文件稍大。

这些更改是对 Go 链接器进行现代化改造的多版本项目的一部分,这意味着将来的版本中有望对链接器进行其他改进。

现在,链接器默认将 -buildmode=pieon linux/amd64 和设置为内部链接模式 linux/arm64,因此这些配置不再需要 C 链接器-buildmode=pie 仍然可以使用 -ldflags=-linkmode=externalflag 请求外部链接模式(这是 Go 1.14 中的默认设置 )。

核心库

新增 tzdata 包

Go 1.15 增加了一个新程序包,time/tzdata。该程序包允许将时区数据库嵌入程序中。导入该软件包(如 import _ "time/tzdata")可以使程序找到时区信息,即使本地系统上不存在时区数据库也是如此。你还可以通过使用嵌入时区数据库 -tags timetzdata。两种方法都会使程序的大小增加大约 800 KB。

弃用 X.509 CommonName

CommonName 默认情况下,当不存在“使用者备用名称”时,将 X.509 证书上的字段视为主机名的不推荐使用的旧行为现在被禁用。可以通过将值添加 x509ignoreCN=0GODEBUG 环境变量中来临时重新启用它。

请注意,如果 CommonName 主机名无效,则无论 GODEBUG 设置如何,都将始终忽略该主机名。无效名称包括那些带有字母,数字,连字符和下划线以外的任何字符的名称,以及带有空标签或尾随点的名称。

支持资源嵌入

Go 1.16 新增的 embed 包 提供了对使用 new//go:embed 指令在编译时访问嵌入在程序中的文件的功能。现在可以轻松地将数据文件捆绑到 Go 程序中,使开发更加流畅。Carl Johnson 写了一篇不错的教程,如何使用 Go embed,供大家参考。

弃用 io/ioutil

io/ioutil 程序包定义不明确且难以理解。程序包提供的所有功能已移至其他程序包。该 io/ioutil 软件包仍然存在,并且将像以前一样继续工作,但是我们鼓励新代码(Go 1.16)使用 ioos 软件包中的新定义。这是由导出的名称的新位置的列表 io/ioutil

  • Discard => io.Discard
  • NopCloser => io.NopCloser
  • ReadAll => io.ReadAll
  • ReadDir => os.ReadDir (注意:返回的是 os.DirEntry 切片而不是 fs.FileInfo 切片)
  • ReadFile => os.ReadFile
  • TempDir => os.MkdirTemp
  • TempFile => os.CreateTemp
  • WriteFile => os.WriteFile

go build 不再更改 mod 相关文件

以前的教程里我提到过 go build 会自动下载依赖,这会更新 mod 文件。

Go 1.16 开始这一行为被禁止了。想要安装、更新依赖只能使用 go get 命令,go build 和 go test 将不会再做这类工作。

go install

go install 在 Go 1.16 中也有了不小的变化。

首先是通过 go install my.module/tool@1.0.0 这样在 module 末尾加上版本号,可以在不影响当前 mod 的依赖的情况下安装 golang 程序。

go install 是未来唯一可以安装 golang 程序的命令,go get 的编译安装功能现在可以靠 -d 选项关闭,而未来编译安装功能会从 go get 移除。

也就是说 go 的命令各司其职,不再长臂管辖了

tcp 半连接队列扩容

在 Linux kernel 4.1 以前,golang 设置 tcp 的 listen 队列的长度是从 /proc/sys/net/core/somaxconn 获取的,通常为 4096

而在 4.1 以后 golang 会直接设置半连接队列的长度为 2^32-1 也就是 4294967295

更大的半连接队列意味着可以同时处理更多的新加入请求,而且不用再读取配置文件性能 也会略微提升。

小结

本文总结了 golang 1.14 1.15 1.16 的一些重要新特性,帮助想升级 golang 版本的同学们快速浏览需要注意的点。后续如果有新的发现,会持续更新本文。

参考链接

CatchZeng
Written by CatchZeng Follow
AI (Machine Learning) and DevOps enthusiast.