Go1.13的新变化

之前在研究Gost源码的时候,我写过一篇Go1.11新特性Module,这一次轮到V2Ray了,环境变成了Go1.13+。

说起来,如果我能深入研究Gost下去的话,现在我应该已经非常熟悉Go语言了,包括代理这方面的知识,可惜啊浅尝辄止了。希望这一次,我能够坚持下去。

本文参考自Go 1.13中值得关注的几个变化

语言

  • 增加了数字字面量的表达能力

增加二进制数字字面量,以0b或0B开头

在保留以0开头的八进制数字字面量形式的同时,增加0o0O开头的八进制数字字面量形式

增加十六进制形式的浮点数字面量,以0x0X开头,形如0x10.24p+3的浮点数

为提升可读性,在数字字面量中增加数字分隔符_。分隔符可以用来分隔数字(起到分组提高可读性作用),也可以用来分隔前缀与第一个数字

1
2
3
4
5
6
7
8
9
10
11
12
a := 5_3    // 53
b := 0o700 // 448
b1 := 0O700 // 448
b2 := 0_700 // 448
b3 := 0o_700 // 448
c := 0b111 // 7
c1 := 0B111 // 7
c2 := 0b_111 // 7
f1 := 0x10.1p+0 // 16.0625(小数点后的位数N可以看作16^(-N),p+M可以看成结果*(M+1)次)
f2 := 0x10.1p+1 // 32.125
f3 := 0x10.p-1 // 8 (p-M可以看成结果/M次)
f4 := 0x10.p-2 // 4
  • 取消了移位操作的无符号数的限制
1
2
3
4
5
6
var i int = 5

fmt.Println(2 << uint(i)) // 64(1.13以前)
fmt.Println(2 << i) // 64(1.13以后)
fmt.Println(64 >> uint(i)) // 2
fmt.Println(64 >> i) // 2

注意,只有在go.mod中的go version指示字段为go 1.13及以后的版本,上述语言特性变更才会生效。当然,如果repo下没有go.mod或者单独在某个没有go.mod的目录下使用go 1.13编译器运行上述代码,也是没有问题的

Go Module机制的优化

  • GO111MODULE=auto的行为变化

Go1.12版本中,GO111MODULE默认值为auto。在auto模式下,GOPATH/src下的repo以及在GOPATH之外的repo依然使用GOPATH mode,不使用go.mod来管理依赖。

Go1.13版本中,module mode优先级提升,GO111MODULE的默认值仍然是auto,但在这个auto下,无论是在GOPATH/src下的repo还是GOPATH之外的repo,只要目录下有go.mod,go编译器都会使用go module来管理依赖。

  • GOPROXY有默认值且支持设置多个代理

之前的版本,GOPROXY环境变量默认为空。go编译器直接与类似github.com这样的代码托管站点通信并获取相关依赖库的数据。一些第三方的GOPROXY服务发布后,迁移到go module的gopher们发现,大多数情况下通过proxy获取依赖包数据的速度要远高于直接从代码托管站点获取的速度,因此GOPROXY总是会配置上一个值。Go核心团队也希望Go能有一个项nodejs那样的中心化的module仓库为大家提供服务。于是Go1.13中将https://proxy.golang.org作为GOPROXY环境变量的默认值,这也是Go官方提供的GOPROXY服务。

同时GOPROXY支持设置多个proxy的列表(多个之间采用,分隔),Go编译器会按顺序尝试列表中的proxy以获取依赖包,但是当有proxy server服务不可达或者返回http状态码不为404也不是410时,go会终止数据获取。

Go1.13中,GOPROXY的默认值为https://proxy.golang.org,direct。当官方代理返回404或410时,Go编译器会尝试直接连接依赖module的代码托管站点。

由于国内无法访问Go官方的proxy,可以使用自己搭建的代理,也可以用国内gopher们搭建的代理

1
export GOPROXY=https://goproxy.cn,自己搭建的国外VPS的代理,direct
  • GOSUMDB

go在go module启用时在本地建立一个go.sum文件,用来储存依赖包特定版本的加密校验和。同时,Go维护下载的软件包的缓存,并在下载时计算并记录每个软件包的加密校验和。在正常操作中,go命令对照这些预先计算的校验和去检查某repo下的go.sum文件,而不是每次命令调用时都重新计算它们。

日常开发中,特定module版本的校验和永远不会改变。每次运行或构建时,go命令都会通过本地的go.sum去检查其本地缓存副本的校验和是否一致。如果校验和不匹配,则go命令将报告安全错误,并拒绝允许构建或运行。在这种情况下,重要的是找出正确的校验和,确定是go.sum错误还是下载的代码是错误的。如果go.sum中尚未包含已下载的module,并且该模块是公共module,则go命令将查询Go校验和数据库以获取正确的校验和数据存入go.sum。如果下载的代码与校验和不匹配,则go命令将报告不匹配并退出。

Go1.13提供了GOSUMDB环境变量用于配置Go校验和数据库的服务地址(和公钥)。其默认值为sum.golang.org,这也是Go官方提供的校验和数据库服务(大陆gopher可以使用sum.golang.google.cn)。出于安全考虑,建议保持GOSUMDB开启。如果因为某些因素无法访问GOSUMDB,可以通过下面命令将其关闭

1
go env -w GOSUMDB=off

GOSUMDB关闭后,仅能使用本地的go.sum进行包的校验和校验了。

  • 面向私有模块的GOPRIVATE

有了GOPROXY后,公共module的数据获取变得容易多了。但是如果依赖的是企业内部module或者托管站点上的private库,通过GOPROXY获取显然会失败,除非你搭建了自己的公私均可的goproxy server,并将其设置到GOPROXY中。

Go1.13提供了GOPRIVATE变量,用于知识哪些仓库下的module是私有,不需要通过GOPROXY下载,也不需要通过GOSUMDB去验证其校验和。不过要注意的是GONOPROXY和GONOSUMDB可以重写GOPRIVATE中的设置,因此设置时要谨慎。

Go错误处理优化

在Go1.13中Go核心团队落实了两点错误处理优化问题

通过标准库增加了errors.ls和As函数来解决error value比较问题

增加errors.Unwrap来解决error unwrap问题

并且Go通过fmt.Errorf中新增%w动词来协助gopher快速创建一个包装错误,创建的error变了实现了下面的接口

1
2
3
4
//匿名接口
interface {
Unwrap() error
}

性能

  • defer性能提升

defer语法让gopher在进行资源(文件、锁)释放的过程中变得优雅很多,也不容易出错。但在性能敏感的应用中,defer带来的性能负担也是gopher必须要权衡的问题。在Go1.13中,Go核心团队对defer性能做了大幅优化。

  • 优化后的逃逸分析

逃逸分析(escape analysis)让编译器在选择究竟将变了分配在栈上还是堆上的时候更精确。在以前版本分配到堆上的变量,在Go1.13中可能就会分配到栈上,从而减少内存分配的次数,一定程度上减轻GC的压力,达到性能提升的目的。

  • sync包中的Mutex、RWMutex的方法的inline化

其它变化

  • Go1.13支持Andriod10le ,对MacOS的需求至少10.11版本(我的Yosemite系统终于无法安装Go1.13了,但是我可以用虚拟机里的Win10来安装Go1.13)
  • godoc不再和go、gofmt放入go release版中,需要godoc的,单独从golang.org/x/tools/cmd/godoc中下载安装
  • crypto/tls默认开启tls1.3支持
  • unicode包支持的unicode标准从10.0升级到11.0版本

总结

老实将,大部分的变化我看到的还是云里雾里的。总之,还是先从Go基础开始学起吧。

avatar

chilihotpot

You Are The JavaScript In My HTML