1.6.go test -v 现在将 t.Log 输出流式传输,而不是在所有测试数据结束时输出
2.goroutine 支持异步抢占
在Go1.1版本中,调度器还不支持抢占式调度,只能依靠 goroutine 主动让出 CPU 资源,存在非常严重的调度问题。
Go1.12中编译器在特定时机插入函数,通过函数调用作为入口触发抢占,实现了协作式的抢占式调度。但是这种需要函数调用主动配合的调度方式存在一些边缘情况,就比如说下面的例子:
func main() {
runtime.GOMAXPROCS(1)
go func() {
for {
}
}()
time.Sleep(time.Millisecond)
println("OK")
}
上面代码中,其中创建一个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。
3.testing 包新增CleanUp 方法
testing包的T、B和TB都加上了CleanUp方法,它将以后进先出的方式执行 f(如果注册多个的话)。
如下代码,输出结果是 test cleanup, clear resource:
func TestCleanup(t *testing.T) {
t.Cleanup(func() {
t.Log("clear resource")
})
t.Log("test cleanup")
}
#### 4.允许嵌入具有重叠方法集的接口
下面接口定义在 Go1.14 之前是不允许的
type ReadWriteCloser interface {
io.ReadCloser
io.WriteCloser
}
因为 io.ReadCloser 和 io.WriteCloser 中 Close 方法重复了,编译时会提示:duplicate method Close。在Go1.14中支持了这种重复接口集
go1.15
1. 1.15版本变化
1.1.对Go链接器的实质性改进
1.2.改进了对高核心计数的小对象的分配
1.3.X.509 CommonName弃用
1.4.GOPROXY支持跳过返回错误的代理
1.5.新增了一个time/tzdata包
1.6.核心库的一些改进
2. 核心库的一些改进
2.1 time.Ticker增加了一个Reset方法支持改变ticker的duration。 time.Ticker是一个周期性的定时器,内置一个周期性传递时间的Channel。 使用time.NewTicker(d Duration)函数创建一个Ticker,这个Ticker内置一个通道字段,每个时间间隔会向这个通道发送当前的时间。ticker会调整时间间隔或者丢弃消息以适应反应慢的接收者。
func TestTickerReset(t *testing.T) {
wait := make(chan struct{})
ticker := time.NewTicker(time.Second * 1)
go func() {
defer close(wait)
for i := 0; i < 5; i++ {
t.Log(<-ticker.C)
if i == 2 {
ticker.Reset(time.Second * 2)
}
}
}()
<-wait
}
2.2 time/tzdata是Go 1.15新增加的包,当系统找不到时区数据时,通过导入这个包,可以在程序中内嵌时区数据。 导入这个包会使程序大小增加大约800KB,注意time/tzdata这个包应该是在程序的main包中导入的,而不要在一个libary项目中导入和使用。 另外也可以通过编译时传递-tags timetzdata来实现同样的效果。
3.panic展现形式变化
在Go 1.15之前,如果传给panic的值是bool, complex64, complex128, float32, float64, int, int8, int16, int32, int64, string, uint, uint8, uint16, uint32, uint64, uintptr等原生类型的值,那么panic在触发时会输出具体的值,比如:
package main
func foo() {
var i uint32 = 17
panic(i)
}
func main() {
foo()
}
1.15以上版本:
panic: main.myint(27)
goroutine 1 [running]:
main.bar(...)
/Users/chengaosheng/go/test/test.go:63
main.main()
/Users/chengaosheng/go/test/test.go:56 +0x39
exit status 2
1.15以下版本:
panic: (main.myint) (0x105fca0,0xc00008e000)
goroutine 1 [running]:
main.bar(...)
/Users/tonybai/go/src/github.com/bigwhite/experiments/go1.15-examples/runtime/panic.go:12
main.main()
/Users/tonybai/go/src/github.com/bigwhite/experiments/go1.15-examples/runtime/panic.go:17 +0x39
exit status 2
Go 1.15针对此情况作了展示优化,即便是派生于这些原生类型的自定义类型变量,panic也可以输出其值。
4. 标准库
4.1 增加json解码限制
json包是日常使用最多的go标准库包之一,在Go 1.15中,go按照json规范的要求,为json的解码增加了一层限制。如果一旦传入的json文本数据缩进深度超过maxNestingDepth,那json包就会panic。当然,绝大多数情况下,我们是碰不到缩进10000层的超大json文本的。因此,该limit对于99.9999%的gopher都没啥影响。
4.2 reflect包
Go 1.15版本之前reflect包
例子:
package main
import "reflect"
type u struct{}
func (u) M() { println("M") }
type t struct {
u
u2 u
}
func call(v reflect.Value) {
defer func() {
if err := recover(); err != nil {
println(err.(string))
}
}()
v.Method(0).Call(nil)
}
func main() {
v := reflect.ValueOf(t{}) // v := t{}
call(v) // v.M()
call(v.Field(0)) // v.u.M()
call(v.Field(1)) // v.u2.M()
}
1.15以上版本:
172-15-70-45:test chengaosheng$ go run test.go
M
reflect: reflect.Value.Call using value obtained using unexported field
reflect: reflect.Value.Call using value obtained using unexported field
我们看到reflect无法调用非导出字段u和u2的导出方法了。但是reflect依然可以通过提升到类型t的方法来间接使用u的导出方法,正如运行结果中的第一行输出。
**这一改动可能会影响到遗留代码中使用reflect调用以类型嵌入形式存在的非导出字段方法的代码**,如果你的代码中存在这样的问题,可以直接通过提升(promote)到包裹类型(如例子中的t)中的方法(如例子中的call(v))来替代之前的方式。
go1.16
1.Go 语言所打包的二进制文件中会包含配置文件的联同编译和打包
无法将静态资源编译打包进二进制文件的话,通常会有两种解决方法:
第一种是识别这类静态资源,是否需要跟着程序走。
第二种就是考虑将其打包进二进制文件中。
第二种情况的话,Go 以前是不支持的,大家就会去借助各种花式的开源库,例如:go-bindata/go-bindata 来实现。
但从在 Go1.16 起,Go 语言自身正式支持了该项特性
演示代码:
import _ "embed"
//go:embed hello.txt
var s string
func main() {
print(s)
}
首先在对应目录下创建hello.txt文件并写"Hello world"
在代码中编写了最为核心的 //go:embed hello.txt 注解。注解的格式很简单,就是 go:embed 指令声明,外加读取的内容的地址,可支持相对和绝对路径。
输出结果:
Hello world
读取到静态文件中的内容后自动赋值给了变量 s,并且在主函数中成功输出。
而针对其他的基础类型,Go embed 也是支持的:
//go:embed hello.txt
var s string
//go:embed hello.txt
var b []byte
//go:embed hello.txt
var f embed.FS
func main() {
print(s)
print(string(b))
data, _ := f.ReadFile("hello.txt")
print(string(data))
}
输出结果:
Hello world
Hello world
Hello world
我们同时在一个代码文件中进行了多个 embed 的注解声明。
并且针对 string、slice、byte、fs 等多种类型进行了打包,也不需要过多的处理,非常便利。
2.拓展用法:
除去基本用法完,embed 本身在指令上也支持多种变形:
//go:embed hello1.txt hello2.txt
var f embed.FS
func main() {
data1, _ := f.ReadFile("hello1.txt")
fmt.Println(string(data1))
data2, _ := f.ReadFile("hello2.txt")
fmt.Println(string(data2))
}
在指定 go:embed 注解时可以一次性多个文件来读取,并且也可以一个变量多行注解:
//go:embed hello1.txt
//go:embed hello2.txt
var f embed.FS
也可以通过在注解中指定目录 helloworld,再对应读取文件:
//go:embed helloworld
var f embed.FS
func main() {
data1, _ := f.ReadFile("helloworld/hello1.txt")
fmt.Println(string(data1))
data2, _ := f.ReadFile("helloworld/hello2.txt")
fmt.Println(string(data2))
}
同时既然能够支持目录读取,也能支持贪婪模式的匹配:
//go:embed helloworld/*
var f embed.FS
embed.FS 也能调各类文件系统的接口,其实本质是 embed.FS 实现了 io/fs 接口
3.只读属性
在 embed 所提供的 FS 中,其实可以发现都是打开和只读方法:
type FS
func (f FS) Open(name string) (fs.File, error)
func (f FS) ReadDir(name string) ([]fs.DirEntry, error)
func (f FS) ReadFile(name string) ([]byte, error)
根据此也可以确定 embed 所打包进二进制文件的内容只允许读取,不允许变更。
更抽象来讲就是在编译期就确定了 embed 的内容,在运行时不允许修改,保证了一致性。
本文来自:简书
感谢作者:陈氏美
查看原文:go特性总结(1.13~1.16)
相关阅读 >>
用 wasmedge 中的嵌入式 webassembly 函数扩展 Golang 应用程序
更多相关阅读请进入《Go》频道 >>

Go语言101
一个与时俱进的Go编程知识库。