Go1.5至Go1.14的版本更新史

我们现在使用的是Go 1.7.6版本。至于为什么从1.5开始总结,这是一个很重要的版本,实现方面有很多变化。最重要的是实现了自举。本文列出一些重大变化以及对我们升级有用的信息。

Go 1.5

该版本主要变化有以下几点

  • 实现自举,使用少量汇编,用GO写GO。实现自举的目的是为了能更好的对GC管理优化
  • 引入并发垃圾回收器,大幅降低GC延迟(Stop The World)
  • 默认情况下,Go 程序运行时的 GOMAXPROCS 会被设置为可用的核数,之前默认为 1。再也不用手动去设置

Map literals

Go1.4之前我们需要这样写代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
package main

import (
"fmt"
)

type i32 struct {
value int
}

func main() {
var i = []i32{{1}, {2}}
var m = map[i32]string{
i32{3}:"i32=3",
i32{4}:"i32=4",
}
fmt.Println(i,m)
}

可以看到,对于i32类型来说,在初始化一个切片时,无需显式的带上元素类型名称i32,即

1
var i = []i32{{1}, {2}}

而不是需要传入名称:

1
var i = []i32{i32{1}, i32{2}}

但是当i32作为map类型的key时,初始化map时则需要带上元素类型i32。在Go 1.5版本修复了。也就是说,下面的代码在Go 1.5以及之后版本中可以顺利编译通过

1
2
3
4
5
6
7
8
func main() {
var i = []i32{{1}, {2}}
var m = map[i32]string{
{3}:"i32=3",
{4}:"i32=4",
}
fmt.Println(i,m)
}

GO 1.6

该版本主要对GO的生态进行了调整

  • 添加linux/mips64linux/mips64leandroid/386平台的实验性支持。
  • go tool工具链调整,对位于我们升级意义不大。
  • 运行时和大面积的标准库BUG修复和调整。

运行时

现在如果我们并发的map进行操作,GO会检测并抛出错误。Go 1.6之前版本,一旦程序以panic方式退出,runtime便会将所有goroutine的stack信息打印出来,现在只会打印正在running的goroutine的stack信息。

核心库和标准包

HTTP/2

Go 1.6已经标准库中已经支持HTTP/2。并且当使用https时,client和server端将自动默认使用HTTP/2协议。

Templates

Go templates的空白字符包括:空格、水平tab、回车和换行符。Go 1.6提供了来帮助大家去除action前后的空白字符。示例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
package main

import (
"log"
"os"
"text/template"
)

func main() {
var t = template.Must(template.New("tmpl").Parse(`
{{"hello" -}}
<br>
{{- "world"}}
`))

err := t.Execute(os.Stdout, nil)
if err != nil {
log.Println("executing template:", err)
}
}

输出hello<br>world<br>左右的空白将会被清除。

Tls

crypto/tls 包现在如果没有指定证书也可以监听。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
package main

import (
"crypto/tls"
"fmt"
)

func main() {
var config = &tls.Config{
GetCertificate: func(info *tls.ClientHelloInfo) (certificate *tls.Certificate, e error) {
return nil, nil
},
}
_, err := tls.Listen("tcp4", ":2020", config)
if err != nil {
fmt.Println(err)
}
}

Go 1.7

该版本的主要是对性能的影响

  • 实验性的支持linux/s390xplan9/arm
  • 编译性能提升,编译出的文件变小
  • 运行时和标准库BUG修复和调整

HTTP Tracing

引用net/http/httptrace包对http请求进行追踪测试

测试

testing现在支持子测试(subtest),更好的支持表驱动测试和更细的粒度测试。

表格驱动测试:有时也被归为关键字驱动测试(keyword-driven testing,是针对自动化测试的软件测试方法,它将创建测试程序的步骤分为规划及实现二个阶段。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
package main

import "testing"

func TestEq(t *testing.T) {
var s1 = []int{1, 2, 3}
var s2 = []int{1, 4, 3}
for k1, v1 := range s1 {
v2 := s2[k1]
if v1 != v2 {
t.Errorf("s1:%d != s2:%d", v1, v2)
}
}
}

平常我们是使用此方法测试,s1是我们期望的结果,我们改造一下使用subtest

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
// sub_test.go
package main

import "testing"

func TestEq(t *testing.T) {
var s1 = []int{1, 2, 3}
var s2 = []int{1, 4, 3}
for k1, v1 := range s1 {
v2 := s2[k1]
t.Run("index 0", func(t *testing.T) {
if k1 != 0 {
t.Skip(k1)
}
assertEq(v1, v2, t)
})
t.Run("index 1", func(t *testing.T) {
if k1 != 1 {
t.Skip(k1)
}
assertEq(v1, v2, t)
})
}
}

func assertEq(v1, v2 int, t *testing.T) {
if v1 != v2 {
t.Errorf("got %d; want %d", v2, v1)
}
}
1
2
3
=== RUN   TestEq/index_1#01
--- FAIL: TestEq/index_1#01 (0.00s)
sub_test.go:27: got 4; want 2

测试结果中index_1是我们子测试的名称,这样我们可很清楚的知道那部分测试是有问题的。

核心标准库

Context

Go 1.7 已经将context上下文包引入到标准库中,可以直接import,轻松进行并发控制平滑退出

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
package main

import (
"context"
"fmt"
"time"
)

func main() {
ctx, cancel := context.WithTimeout(context.Background(), 3*time.Second)
defer cancel()
go func(ctx context.Context) {
for {
select {
case <-ctx.Done():
fmt.Println("监控超时退出:", ctx.Err())
return
default:
fmt.Println("正在监控Goroutine")
time.Sleep(1 * time.Second)
}
}
}(ctx)

time.Sleep(5 * time.Second)
}
1
2
3
4
正在监控Goroutine
正在监控Goroutine
正在监控Goroutine
监控超时退出: context deadline exceeded

Strings

stringsReaderRead方法如果没有数据将会返回长度0和io.EOF 之前的版本不会产生错误!

Time

time.Duration 如果0时段,现在time.Duration.String0s而不是0

1
2
3
4
5
6
7
8
9
10
package main

import (
"fmt"
"time"
)

func main() {
fmt.Println(time.Duration(0))
}

Go 1.8

此版本调整的地方

  • GC延迟大幅降低,通常是100ms,大部分情况下都是10ms左右
  • 正式支持linux/mips64linux/mips64le
  • 移除go tool yacc
  • 编译性能提升15%
  • 默认安装Go之后 $GOPATH 默认路径为 $HOME/go,如果是Windows则是%USERPROFILE%/go
  • Plugin的初步支持

语言

1.8 开始,tag不一样的struct可以执行显性转化。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
package main

import "fmt"

type A struct {
Value int `json:"value"`
}

type B struct {
Value int `json:"b_value"`
}

func main() {
var a = &A{1}
var b = (*B)(a)

fmt.Println(b)
}

核心和标准库

Sort

1.8 之前版本,我们对自定义的slice排序需要实现sort.Inteface接口

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
package main

import (
"fmt"
"sort"
)

type Student struct {
Score int
}

// 实现sort.Inteface接口
type Person []Student

func (p Person) Len() int { return len(p) }
func (p Person) Less(i, j int) bool { return p[i].Score < p[j].Score }
func (p Person) Swap(i, j int) { p[i], p[j] = p[j], p[i] }

var students = Person{{90},{83},{87}}

func main() {
sort.Sort(students)
fmt.Println(students)
}

1.8 之后,我们可以使用更快的方式

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
package main

import (
"fmt"
"sort"
)

type Student struct {
Score int
}

type Person []Student

var students = Person{{90}, {83}, {87}}

func main() {
sort.Slice(students, func(i, j int) bool {
return students[i].Score < students[j].Score
})
fmt.Println(students)
}

两种结果是一样的,省掉额外代码实现sort.Inteface接口

HTTP/2 Push

Go 1.8新增了对HTTP/2 Push的支持。

h2push

简单的介绍下HTTP/2 Push:从图中当Browser向Server发起Get page.html请求后,在同一条TCP Connection上,Server主动将style.css和image.png两个资源文件也推送给了Browser。这是由于Server端启用了HTTP/2 Push机制,并预测判断Browser很可能会在接下来发起Get style.css和image.png两个资源的请求。这些主动推送给Browser的资源很可能是Browser所不需要的或是已经在Browser cache中存在的资源,会造成带宽资源浪费。

HTTP Server 优雅退出

1.8 新增优雅退出的方法

1
func (srv *Server) Shutdown(ctx context.Context) error

我们传入一个上下文,当前有未处理完的active connections。将等待处理完,当然我们也不能无限等待下去,我们可以指定上下文的超时时间。

Go 1.9

该版本几点变化

  • 引入类型别名
  • 并行编译,速度大大提升
  • ./…不再匹配vendor目录
  • 新增并发map支持sync.Map

语言

新的类型别名,类型别名声明方式

1
type A = B

type alias并未创造新类型,只是源类型的“别名”,在类型信息上与源类型一致,因此可以直接赋值。

1
A.BValue = 2

也可以使用源类型的方法,但是不能定义新方法

1
2
3
A.Bfunc()

func (a *A) Afunc() // Error: cannot define new methods on non-local type int

核心和标准包

Sync

新增并发map的支持,sync.Map结构

Database/sql

如果Tx.Stmt不可用,将会使用缓存好的Stmt,这样是避免每次重新prepare

Math

Go 1.9提供了高性能math/bits 应对bit位的操作这个问题。

Go 1.10

该版本变化不大,几点特色:

  • 默认的GOROOT,开发者无需显式设置GOROOT环境变量,go程序会自动根据自己所在路径推导出GOROOT的路径。
  • 通过cache实现增量构建
  • 新的环境变量GOTMPDIR
  • test的文件没有变化,那么test的结果会被缓存

核心和标准包

Strings

该包中添加了一个Builder类型,可以用来字符串拼接。降低字符串的拼接的拷贝带来的内存分配和释放的性能损失。

Go 1.11

该版本添加了WebAssembly的支持,最重要是引入了包管理go moudle。启用go module

1
export GO111MODULE=on

初始化项目

1
go mod init your-project

此时使用go build会自动下载包依赖构建。

对于被墙的包,可以设置代理

1
export GOPROXY=https://goproxy.cn

Go 1.12

该版本变化也很小

  • GO111MODULE=on时,获取go module不再显式需要go.mod,go get 时不需要创建go.mod文件了-
  • 添加对TLS 1.3的支持,默认不开启
  • go安装包中移除go tour

Go 1.13

该版本变化也很小

  • GO111MODULE的默认值继续auto,但是只要当前目录包含go.mod文件或位于go.mod文件的目录下,auto设置就会激活go命令的模块

  • Go1.13这个版本引入了一个新的环境变量GOPRIVATE,用来指示私有的模块路径。

    1
    2
    # 设置不走proxy的私有仓库,多个用逗号相隔
    go env -w GOPRIVATE=*.corp.example.com
  • 1.13版本提高了defer性能达30%。

语言

  • 新增数字表示方式0b0010 (二进制)、0o1276(八进制)、0x.8p-0(十六进制浮点)

核心和标准包

Sync

sync包的Once.Do方法比之前快了两倍,Mutex/RWMutex方法的速度提高了10%。

Go 1.14

版本主要还是标准包的修改,语言结构上略有变化。

语言

interface类型相同的方法可以覆盖了,下面的代码在1.14之前无法编译通过

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
package main

type Person interface {
Speak()
}

type Student interface {
Speak()
}

type ChineseStudent interface {
Person
Student
}

func main() {

}

1.14之前版本报错

1
2
# command-line-arguments
./interface.go:14: duplicate method Speak

工具链

  • 1.14已经完全建议开发者使用go module版本管理。
  • go.mod 包中的+incompatible 标识的包本,如果模块的最新版本包含go.mod文件,则除非明确要求或已经使用该版本,否则go get将不再升级到该模块的不兼容版本。

核心和标准包

crypto/tls

  • 移除SSLv3的支持

reflect

  • StructField元素添加PkgPath字段,StructOf支持创建未导出的字段。

hash/maphash

  • 标准库的新成员,用与字节哈希序列化,产生的结果单处理器上是一致性的,不同的处理器将产生不同结果,且不是密码安全的。