Go 1.14简介

最新的Go发布版本1.14在Go 1.13之后六个月到来。它的大部分更改是在工具链,运行时和库的实现中进行的。与往常一样,该发行版保留了Go 1 兼容性的承诺。我们希望几乎所有Go程序都能像以前一样继续编译和运行。
go现在 ,该命令中的模块支持已准备好用于生产,并且我们鼓励所有用户迁移到Go模块进行依赖项管理。如果您无法迁移由于围棋工具链出现问题,请确保问题有一个 悬而未决的问题 提出。(如果问题不在Go1.15里程碑上,请告诉我们为什么它阻止您迁移,以便我们可以适当地对它进行优先级排序。)

语言变更

根据重叠接口建议,Go 1.14现在允许使用重叠的方法集嵌入接口
这应该是Go1.14在语言层面上最大的改动了,如下的接口定义在Go1.14之前是不允许的:

  1. type ReadWriteCloser interface {
  2. io.ReadCloser
  3. io.WriteCloser
  4. }

因为io.ReadCloser和io.WriteCloser中Close方法重复了,编译时会提示:duplicate method Close。Go1.14开始允许相同签名的方法可以内嵌入一个接口中,注意是相同签名,下边的代码在Go1.14依然不能够执行,因为MyCloser接口中定义的Close方法和io.ReadCloser接口定义的Close方法的签名不同。

  1. type MyCloser interface {
  2. Close()
  3. }
  4. type ReadWriteCloser interface {
  5. io.ReadCloser
  6. MyCloser
  7. }

将MyCloser的Close方法签名修改为:

  1. type MyCloser interface {
  2. Close() error
  3. }

这样代码就可以在Go1.14版本中build了!轻松实现接口定义的重载。

工具类

关于Go1.14中对工具的完善,主要说一下go mod和go test,Go官方肯定希望开发者使用官方的包管理工具,Go1.14完善了很多功能,如果大家在业务开发中对go mod有其他的功能需求,可以给官方提issue。
go mod 主要做了以下改进:

  • incompatiable versions:如果模块的最新版本包含go.mod文件,则除非明确要求或已经要求该版本,否则go get将不再升级到该模块的不兼容主要版本。直接从版本控制中获取时,go list还会忽略此模块的不兼容版本,但如果由代理报告,则可能包括这些版本。
  • go.mod文件维护:除了go mod tidy之外的go命令不再删除require指令,该指令指定了间接依赖版本,该版本已由主模块的其他依赖项隐含。除了go mod tidy之外的go命令不再编辑go.mod文件,如果更改只是修饰性的。
  • Module下载:在module模式下,go命令支持SVN仓库,go命令现在包括来自模块代理和其他HTTP服务器的纯文本错误消息的摘要。如果错误消息是有效的UTF-8,且包含图形字符和空格,只会显示错误消息。

go test的改动比较小:

  • go test -v现在将t.Log输出流式传输,而不是在所有测试数据结束时输出。

Runtime

defer

性能再次提升

  1. import (
  2. "testing"
  3. )
  4. type channel chan int
  5. func NoDefer() {
  6. ch1 := make(channel, 10)
  7. close(ch1)
  8. }
  9. func Defer() {
  10. ch2 := make(channel, 10)
  11. defer close(ch2)
  12. }
  13. func BenchmarkNoDefer(b *testing.B) {
  14. for i := 0; i < b.N; i++ {
  15. NoDefer()
  16. }
  17. }
  18. func BenchmarkDefer(b *testing.B) {
  19. for i := 0; i < b.N; i++ {
  20. Defer()
  21. }
  22. }

image.png
可以看到,Go1.13版本调用defer关闭channel的性能开销还是蛮大的,op几乎差了30ns。
Go1.14版本使用defer关闭channel几乎0开销!
关于这一改进,官方给出的回应是:Go1.14提高了defer的大多数用法的性能,几乎0开销!defer已经可以用于对性能要求很高的场景了**。**
关于defer,在Go1.13版本已经做了一些的优化,相较于Go1.12,defer大多数用法性能提升了30%。而Go1.14的此次改进更是激动人心!关于Go1.14对defer优化的原理和细节,笔者还没有收集到参考资料,相信很快就会有大神整理出来,大家可以关注一下。关于Go语言defer的设计原理、Go1.13对defer做了哪些改进,推荐给大家下面几篇文章:

goroutine

goroutine支持异步抢占
Go语言调度器的性能随着版本迭代表现的越来越优异,我们来了解一下调度器使用的G-M-P模型。先是一些概念:

  • G(Goroutine): goroutine,由关键字go创建
  • M(Machine): 在Go中称为Machine,可以理解为工作线程
  • P(Processor) : 处理器 P 是线程 M 和 Goroutine 之间的中间层(并不是CPU)

M必须持有P才能执行G中的代码,P有自己本地的一个运行队列runq,由可运行的G组成,下图展示了 线程 M、处理器 P 和 goroutine 的关系。
image.png
Go语言调度器的工作原理就是处理器P从本地队列中依次选择goroutine 放到线程 M 上调度执行,每个P维护的G可能是不均衡的,为此调度器维护了一个全局G队列,当P执行完本地的G任务后,会尝试从全局队列中获取G任务运行(需要加锁),当P本地队列和全局队列都没有可运行的任务时,会尝试偷取其他P中的G到本地队列运行(任务窃取)。
在Go1.1版本中,调度器还不支持抢占式调度,只能依靠 goroutine 主动让出 CPU 资源,存在非常严重的调度问题:

  • 单独的 goroutine 可以一直占用线程运行,不会切换到其他的 goroutine,造成饥饿问题
  • 垃圾回收需要暂停整个程序(Stop-the-world,STW),如果没有抢占可能需要等待几分钟的时间,也可能永远hang住,导致整个程序无法工作

Go1.12中编译器在特定时机插入函数,通过函数调用作为入口触发抢占,实现了协作式的抢占式调度。具体例子看https://www.yuque.com/jiangwei-3zuwt/rieow9/ov5xxe
但是这种需要函数调用主动配合的调度方式存在一些边缘情况,就比如说下面的例子:

  1. package main
  2. import (
  3. "runtime"
  4. "time"
  5. )
  6. func main() {
  7. runtime.GOMAXPROCS(1)
  8. go func() {
  9. for {
  10. }
  11. }()
  12. time.Sleep(time.Millisecond)
  13. println("OK")
  14. }

其中创建一个goroutine并挂起, main goroutine 优先调用了 休眠,此时唯一的 P 会转去执行 for 循环所创建的 goroutine,进而 main goroutine 永远不会再被调度。换一句话说在Go1.14之前,上边的代码永远不会输出OK,因为这种协作式的抢占式调度是不会使一个没有主动放弃执行权、且不参与任何函数调用的goroutine被抢占。
Go1.14 实现了基于信号的真抢占式调度解决了上述问题。Go1.14程序启动时,在 runtime.sighandler 函数中注册了 SIGURG 信号的处理函数 runtime.doSigPreempt,在触发垃圾回收的栈扫描时,调用函数挂起goroutine,并向M发送信号,M收到信号后,会让当前goroutine陷入休眠继续执行其他的goroutine。
Go语言调度器的实现机制是一个非常深入的话题。下边推荐给读者几篇文章,特别值得探索学习:

time.Timer

time.Timer定时器性能得到“巨幅”提升
image.png
从基准测试的结果可以看出Go1.14 time包中AfterFunc、After、Ticker的性能都得到了“巨幅”提升。

核心库

新增:hash/maphash https://golang.org/pkg/hash/maphash/
reflect:在StructField元素中设置了PkgPath字段,StructOf支持使用未导出字段创建结构类型
更多参考:https://golang.org/doc/go1.14

参考:https://studygolang.com/articles/26529?fr=sidebar