译自 tylerchr 的What's Coming in Go 1.8。
随着Go 1.8 新特性的开发工作已经冻结,Go 1.8 将在2017年2月左右发布,现在让我们看一些在Go 1.8更有趣的API的改变。
@H_301_14@HTTP server connection draining Brad Fitzpatrick最近关闭了一个将近四年的issue,这个issue请求实现http.Server
的连接耗尽(draining)的功能。现在可以调用srv.Close
可以立即停止http.Server
,也可以调用srv.Shutdown(ctx)
等待已有的连接处理完毕(耗尽,draining,github.com/tylerb/graceful的用户应该熟悉这个特性)。
下面这个例子中,服务器当收到SIGINT
信号后(^C
)会优雅地关闭。
import(
" context "
io log net/http os os/signal time "
)
funcmain(){
//subscribetoSIGINTsignals
stopChan:=make(chanos.Signal)
signal.Notify(stopChan,os.Interrupt)
mux:=http.NewServeMux()
mux.Handle( / ",http.HandlerFunc(func(whttp.ResponseWriter,r*http.Request){
time.Sleep(5*time.Second)
io.WriteString(w,Finished! ")
}))
srv:=&http.Server{Addr: :8081
gofunc(){
//serviceconnections
iferr:=srv.ListenAndServe();err!=nil{
log.Printf( listen:%s\n }
}()
<-stopChan//wait forSIGINT
log.Println( Shuttingdownserver ")
//shutdowngracefully,butwaitnolongerthan5secondsbeforehalting
ctx,_:=context.WithTimeout(context.Background(),5*time.Second)
srv.Shutdown(ctx)
log.Println( Servergracefullystopped ")
}
一旦收到SIGINT
信号,服务器会立即停止接受新的连接,srv.ListenAndServe()
会返回http.ErrServerClosed
。srv.Shutdown
会一直阻塞,直到所有未完成的request都被处理完以及它们底层的连接被关闭。
更复杂的处理可以通过context
实现,例如使用context.Timeout
实现最大的关闭等待时间。你可以尝试复制https://github.com/tylerchr/examples/tree/master/draining中的例子并实现它。
http.Pusher
实现 HTTP/2.0 server push
HTTP/2.0 包含 Server Push 特性, 允许 HTTP/2 服务器主动地发送额外的 HTTP response 给客户端,即使客户端没有发送请求。目标是在客户端无需请求的情况下,服务器可以及时地将客户端所需的资源推送给客户端。可以查看wikiHTTP/2 Server Push看具体的例子。
如果一个服务器支持HTTP/2
,提供给 handler 的http.ResponseWriter
会实现http.Pusher
接口。Handler 可以使用这个功能区触发Server Push, 虚拟的请求(synthetic request)可以被注册的 http.Server Handler所处理。
下面的程序处理/index.html
,可以push一个/static/gopher.png
:
funcmain(){
http.Handle( /static/ ./static "))))
http.Handle( /index.html
//serverpush isavailable ifwimplementshttp.Pusher
ifp,ok:=w.(http.Pusher);ok{
p.Push( /static/gopher.png }
//loadthemainpage
w.Header().Set( Content-Type text/html ")
w.Write([]byte(`<imgsrc= "/>`))
}))
http.ListenAndServeTLS( :4430 cert.pem key.pem
}
你可以从https://github.com/tylerchr/examples/serverpush克隆这个例子,下面是在 Chrome 54 访问的结果:
明显地可以在 Initiator 那一列中看到gopher.png
被Push
,你也可以看到标蓝色的gopher.png
先于/index.html
被接收过来,这表明这个资源先于请求之前被推送到客户端。HTML下载完后<img>
可以显示。
有人可能会问如何写一个测试用来校验实现 Server Push的 Handler。因为http.NewTLSServer
没有启动 HTTP/2 服务器,httptest.ResponseRecorder
也没有实现http.Pusher
。我的解决方案是包装httptest.ResponseRecorder
实现Pusher
接口,这里有个例子。
database/sql
包有几个主要的改变,可以让用户更好的控制数据库查询,允许用户更好的利用数据库的特性。
更多的细节可以阅读Daniel Theophanes的文章What is new in database/sql?,他实现了大部分的改变。
@H_301_14@plugin包实现动态插件 新增加的标准库plugin
提供了初步的插件支持,它允许程序可以在运行的时候动态的加载插件。
但是这个库看起来还是bug多多,我甚至不能写一个正常的程序来测试它,但是假定它的使用应该如下面的样子:
packagemain
encoding/hex "
funcHexify(instring)string{
returnhex.EncodeToString([]byte( in))
}
$gobuild-buildmode=sharedhexify.go
//produceshexify.so
//main.go
packagemain
plugin "
funcmain(){
p,_=plugin.Open( hexify.so ")
f:=p.Lookup( Hexify ")
fmt.Println(f.(func(string)string)( gopher "))
//676f70686572
}
在这个例子中,hexify.go
实现了Hexify
函数,它被编译成一个共享库,第二个程序动态加载它。这允许Go程序可以不在编译的时候也能调用其它的库。
别名(aliasing)曾被增加到 Go 1.8 的语言规范中,但是现在又被移除了,看这个说明:this post from Russ Cox,有可能会出现在 Go 1.9中。
这个特性也引起了很多的争议,
typeFoo => pkg.Bar
这个语法定义Foo
是pkg.Bar
别名。Foo
可以用在任何pkg.Bar
出现的地方。以上个例子为例,任何需要类型golang.org/x/net/context
的地方都可以用标准库context
代替,它们是等价的。
别名也可以用在常量、变量、函数等类型上。
这是一个很有争议的特性,可以参考issue 16339和golang-dev post看大家的讨论。因为它从Go 1.8中移除了,大家可以暂时不用关注这个特性了。
@H_301_14@新的slice排序API 统一的slice排序由新的sort.Slice函数实现。它允许任意的slice都可以被排序,只需提供一个回调比较函数即可,而不是像以前要提供一个特定的sort.Interface
的实现。这个函数没有返回值。想其它的排序函数一样,它提供了原地的排序。
下面的例子根据海拔高度排序知名山峰的slice。
Namestring
Elevationint// infeet
}
peaks:=[]Peak{
{ Aconcagua { Denali Kilimanjaro MountElbrus @H_403_616@MountEverest MountKosciuszko MountVinson PuncakJaya }
//doesan in-placesortonthepeaksslice,withtallestpeakfirst
sort.Slice(peaks,func(i,jint)bool{
returnpeaks[i].Elevation>=peaks[j].Elevation
})
//peaks isnowsorted
通过sort.Interface
类型的Len()
和Swap(i,j int)
提供了抽象的排序类型,这是以前的排序方法,而Less(i,j int)
作为一个比较回调函数,可以简单地传递给sort.Slice
进行排序。
- 87b1aaa
encoding/base64
encoder现在有了严格模式. - 6ba5b32
expvar
暴露出来,可以用在其它的mux中. - 003a598伪随机码可以通过
rand.Uint64()
产生 (先前仅支持uint32). - 67ea710增加了一个新的
time.Until
函数,和time.Since
对应.
net/http故意只实现了使用TLS的HTTP/2,你可以查看[issue 14141]https://github.com/golang/go/issues/14141()了解细节。
sort.SliceStable提供了稳定的slice排序,就像以前的sort.Stable
一样。
Go 1.8 一个很大的特性就是性能的提升,包括二进制文件的大小、编译速度和运行速度。
并且非常大的提升就是提供小于100us GC暂停。
net/http提供了更多的超时设置,比如ReadHeaderTimeout
、IdleTimeout
。
一个完整的改动列表:Go 1.8
http://www.jb51.cc/article/p-ykpntkzd-bqg.htmlGo语言已经7岁了!今年8月,Go 1.7如期发布。撰写本稿时,Go 1.8的测试版也出来了。我们正在热切盼望着明年2月的Go 1.8正式版。
如果你关注 TIOBE的编程语言排行榜 就会发现,截止到2016年11月,Go语言从原先的第50多位经过多次上窜已经跃到了第13位,跻入绝对主流的编程语言的行列!这份排行榜每月都会更新,并基于互联网上的程序员老鸟、教学课程和相关厂商的数量进行排名。在国内,从我这几年运营Go语言北京用户组的经历来看,可以明显地感觉到Go语言的在国内的大热。N多初创互联网企业都选用Go语言作为他们的基础技术栈。我还发现,已经有在大数据、机器人等尖端科技领域耕耘的国内公司开始使用Go语言。这门语言现在已经是无孔不入了。
1. 回顾
遥想去年的1.5版本,Go运行时系统和标准库刚完成去C化,转而完全由Go语言和汇编语言重写。到现在,Go的源码已有了较大的改进,Go语言版本的Go语言也更加成熟了。我下面就带领大家一起回顾一下Go语言在2016年做出的那些大动作。
1.1 极速GC
当然,首先要说的还是性能。Go语言本身最大的性能提升依然在GC(garbage collection,垃圾回收)方面。从Go 1.5时标榜的GC耗时百毫秒级,到今天的全并发GC使得耗时达到毫秒级,再到即将发布的Go 1.8由于实施了诸多改进而达成的百微秒级以下的GC耗时,真可谓是突飞猛进!
图1 GC停顿时间——Go 1.5 vs. Go 1.6
图2 GC停顿时间——Go 1.7
在经历了如此变化之后,如果你现在再说你的Go程序的性能瓶颈在GC上,那只能让人侧目了。
当然,Go语言对自身性能的提升远不止于此。
1.2 对HTTP/2的支持
很早以前,Go语言团队就开始跟进HTTP/2草案了。从Go 1.6开始,我们其实已经可以间接地在Go程序中使用到HTTP/2了,应用场景如:使用Go程序开发基于HTTPS协议的服务端和客户端。不过,这一切都是自动适配的,Go官方并未暴露出可以指定或配置HTTP/2模块的任何API。另外,在还未发布的Go 1.8中,HTTP/2还会得到更广泛的支持。
1.3 httptrace包
Go 1.7的标准库中新增了net/http/httptrace代码包(https://godoc.org/net/http/httptrace)。 它提供了一种调试HTTP请求和响应的方式。你可以像下面这样轻易地获取基于HTTP协议的通讯过程的详细信息。
"
fmt net/http/httptrace "
)
funcmain(){
traceCtx:=httptrace.WithClientTrace(context.Background(),&httptrace.ClientTrace{
GetConn:func(hostPortstring){
fmt.Printf( Preparetogetaconnectionfor%s.\n },
GotConn:func(infohttptrace.GotConnInfo){
fmt.Printf( Gotaconnection:reused:%v,fromtheidlepool:%v.\n info.Reused,info.WasIdle)
},
PutIdleConn:func(errerror){
iferr==nil{
fmt.Println( Putaconnectiontotheidlepool:ok. ")
} else{
fmt.Println( Putaconnectiontotheidlepool: }
},
ConnectStart:func(network,addrstring){
fmt.Printf( Dialing(%s:%s).\n },
ConnectDone:func(network,addrstring,errerror){
iferr==nil{
fmt.Printf( Dialisdone.(%s:%s)\n } else{
fmt.Printf( Dialisdonewitherror:%s.(%s:%s)\n }
},
WroteRequest:func(infohttptrace.WroteRequestInfo){
ifinfo.Err==nil{
fmt.Println( Wrotearequest:ok. Wrotearequest: }
},
GotFirstResponseByte:func(){
fmt.Println( Gotthefirstresponsebyte. ")
},
})
req,err:=http.NewRequest( GET http://www.golang.org/ iferr!=nil{
log.Fatal( Fatalerror: }
req=req.WithContext(traceCtx)
_,err=http.DefaultClient.Do(req)
iferr!=nil{
fmt.Fprintf(os.Stderr,0)">Requesterror:%v\n os.Exit(1)
}
}
1.4 子测试强烈建议你动手运行一下这个小程序,享受一下掌控全局的感觉。
Go 1.7中增加了对子测试(https://blog.golang.org/subtests) 的支持,包括功能测试和性能测试。子测试的主要目的是在测试函数中区分和展示因不同的测试参数或测试数据带来的不同的测试结果。请看下面的测试程序。
math/rand strconv testing "
)
//KE代表键-元素对。
typeKEstruct{
keystring
elementint
}
//BenchmarkMapPut用于对字典的添加和修改操作进行测试。
funcBenchmarkMapPut(b*testing.B){
max:=5
varkes[]KE
fori:=0;i<=max;i++{
kes=append(kes,KE{strconv.Itoa(i),rand.Intn(1000000)})
}
m:=make(map[string]int)
b.ResetTimer()
for_,ke:=rangekes{
k,e:=ke.key,ke.element
b.Run(fmt.Sprintf( Key:%s,Element:%#v fori:=0;i<b.N;i++{
m[k]=e+i
}
})
}
}
ok _/Users/haolin/infoq-2016_review_go /demo/subtest 8.678s在程序所在目录下使用go test -run=^$ -bench .命令运行它之后就会看到,针对每一个子测试,go test命令都会打印出一行测试摘要。它们是分离的、独立统计的。这可以让我们进行更加精细的测试,细到每次输入输出。上述打印内容类似: BenchmarkMapPut/Key:_0425,_Element:_498081-4 30000000 40.6 ns/op BenchmarkMapPut/Key:_1540,_Element:_727887-4 30000000 41.7 ns/op BenchmarkMapPut/Key:_2456,_Element:_131847-4 30000000 43.3 ns/op BenchmarkMapPut/Key:_3300,_Element:_984059-4 30000000 46.1 ns/op BenchmarkMapPut/Key:_4694,_Element:_902081-4 30000000 48.4 ns/op BenchmarkMapPut/Key:_5511,_Element:_941318-4 30000000 59.3 ns/op PASS
1.5 vendor目录
在Go 1.5的时候,官方启用了一个新的环境变量——GO15VENDOREXPERIMENT。该环境变量可以启动Go的vendor目录 (https://golang.org/s/go15vendor) 并用于存放当前代码包依赖的代码包。在Go 1.5中,若GO15VENDOREXPERIMENT的值为1则会启动vendor目录。Go 1.6正相反,默认支持vendor目录,当GO15VENDOREXPERIMENT的值为0时禁用vendor目录。到了Go 1.7,官方完全去掉了这个环境变量。这也代表着对vendor目录的正式支持。Go语言的实验特性一般都是按照类似的路数一步步迈向正式版的。
1.6 其他值得一提的改进
@H_301_14@1.6.1 检测并报告对字典的非并发安全访问从Go 1.6开始,Go运行时系统对字典的非并发安全访问采取零容忍的态度。请看下面的程序。
funcmain(){
constworkers=100
varwgsync.WaitGroup
wg.Add(workers)
m:=map[int]int{}
fori:=1;i<=workers;i++{
gofunc(iint){
forj:=0;j<i;j++{
m[i]++
}
wg.Done()
}(i)
}
wg.Wait()
}
Go语言团队一直致力于标准库中众多API的性能提升,并且效果向来显著。我把sort包单拎出来强调是因为sort.Sort函数因性能优化而在行为上稍有调整。在Go 1.6,sort.Sort函数减少了大约10%的比较操作和交换操作的次数,从而获得了20%~50%的性能提升。不过,这里有一个副作用,那就是sort.Sort函数的执行会使排序算法不稳定。所谓不稳定的排序算法,就是排序可能会使排序因子相等的多个元素在顺序上不确定。比如,有如下需要根据长度排序的字符串的切片:
经sort.Sort函数排序后,该切片只几个长度相等的元素golang、erlang和Python的先后顺序可能就不是这样了,可能会变成erlang、golang、python。虽然它依然会依据排序因子(这里是字符串长度)进行完全正确的排序,但是如此确实可能对一些程序造成影响。
如果你需要稳定的排序,可以使用sort.Stable函数取而代之。
@H_301_14@1.6.3 context包进入标准库在Go 1.7发布时,标准库中已经出现了一个名为context的代码包。该代码包原先的导入路径为golang.org/x/context,而后者现在已经不存在了。context包被正式引入标准库,并且标准库中的很多API都因此而做了改变。context.Context类型的值可以协调多个Groutine中的代码执行“取消”操作,并且可以存储键值对。最重要的是它是并发安全的。与它协作的API都可以由外部控制执行“取消”操作,比如:取消一个HTTP请求的执行。
@H_301_14@1.6.4 go tool trace的增强go tool trace自Go 1.5正式加入以来,成为了Go程序调试的又一利器。到了Go 1.7,它已经得到了大幅增强。比如,执行时间的缩短、跟踪信息的丰富,等等。
@H_301_14@1.6.5 unicode包现基于Unicode 9.0Go 1.7升级了unicode包,使它支持Unicode 9.0标准。在这之前,它支持的Unicode 8.0标准。
@H_301_14@1.6.6 新的编译器后端——SSASSA 作为新的编译器后端,可以让编译器生成压缩比和执行效率都更高的代码,并为今后的进一步优化提供了更有力的支持。在性能方面,它可以让程序减少5%至35%的cpu使用时间。
到这里,我向大家展示了Go语言在2016年的一些显著变化。由于篇幅原因,还有很多Go运行时系统和标准库的改动没能提及。尤其是性能方面的改进一直在持续,并潜移默化地为广大Go程序员提供着底层红利。
我强烈建议所有Go程序员紧跟Go语言团队的脚步,升级版本,享受红利。
2. 展望
2.1 新版本
关于展望,莫过于广大Go程序员翘首期盼的Go 1.8了。这里提一下几个如甘霖般的特性。
Go编写的HTTP服务器支持平滑地关闭。这一功能早已由很多第三方代码包实现,但是这次官方终于给出了答案。 支持HTTP/2的Server Push。这个就不多说了,肯定会比Hijack更好用。 新增了plugin包,你可以把一些Go程序作为插件动态地加载进你的程序了。 更广泛的上下文支持,自从标准库中有了context包,它就在很多地方起作用了。很多基于标准库的接口功能都可以执行“取消”操作。在Go 1.8中,范围将进一步扩大,比如:database/sql包和testing包都对上下文进行了支持。 sort包的功能改进,对于切片,我们不用再为了使用它的排序功能去编写某个接口的实现类型。 go test命令有了新的标记:-mutexprofile。该标记用于提供关于锁争用的概要文件。 当然,最值得期待的仍然是Go在性能上的提升,尤其是GC方面,又要有一次飞跃了!另外,defer语句的执行会比之前快整整两倍!cgo的调用开销也降低了将近一半!
2.2技术社区
其实,除了对Go语言本身的展望,我们也应该憧憬Go社区(尤其是国内Go社区)的发展。中国现在已经差不多是Go程序员最多的国家了。如果打开Github上Go语言的Wiki(https://github.com/golang/go/wiki/GoUsers) 你就会发现,那里已经有一个非常长的列表了。其中的公司达到了近200家,相比于2015年年底翻了将近三倍。而且我相信这只是一小部分,只是在Github上有自己的官方组织且对社区有贡献的一部分公司。不过,你可能还会发现,在China那一栏下的公司却只有一家。这是为什么呢?比较值得深思。我想这也许能从侧面反映出对国际技术社区(尤其是开源社区)有贡献的国内公司太少的问题。在2016年12月初举办的一个大型开源技术盛典的讲台上,某开源公司CEO提到了程序员对开源社区应该不只索取更要奉献,这样才能更好地宣传和推销自己。同时,组织机构也不应该成为大家合作的瓶颈。但是,我想国内的实际情况却恰恰相反。我们国内的计算机技术公司,甚至技术驱动的互联网公司,大都没有为开源社区做奉献的习惯,甚至从规章制度上就是明令禁止的。从这方面看,我觉得那个列表中的China栏的惨状也着实不冤。我热切盼望到了明年这个China栏能够变长很多。
不过,从Github以及国内一些代码托管仓库上的Go项目数量上看,国人编写的Go软件其实已经非常多了。近年来崛起的国内Go开源项目已有不少,特别是(按Star数排列)Gogs、Beego、TiDB、Codis、Pholcus、Hprose、Cyclone等等。他们都已经在国际或国内有了一定的影响力。另外,国人或华人参与的国际Go开源项目更是众多,比如很多人熟知的容器技术领域翘楚Docker、Kubernates、Etcd,等等。
当然,除了一些拔尖的人和拔尖的项目。大多数中国Go程序员和爱好者还是只在国内活跃的。国内的很多地方都自行发起了Go语言用户组,包括但不限于:北京、上海、深圳、杭州,大连、香港等。在各个地方举办的Go语言技术聚会也更加专业、更加频繁,同时规模更大。仅在北京,2016年参加此类聚会或活动的人次就将近400,Go语言北京用户组的微信公众号(golang-beijing)的粉丝数也超过了2000。据悉,在2017年,各地的Go语言用户组还会有更大的动作。
我个人认为,如今Go语言的国内推广已经基本完成了科普阶段,现在我们可以实行更加轻松的推波助澜、顺水推舟的推广策略了。由于Go语言的优秀以及不断的进化,现在自发地关注Go语言的人越来越多了,尤其是在高等学府和编程新手的人群中。
Go语言很好学,配套工具完善,开发和运行效率高,应用领域众多,国内社区也很活跃,有各种各样的中文资料和教程,进阶并不难,其工程化理念也相当得民心。如果你不是一点时间都没有的话,我建议你学一学这门简约、高效的编程语言。在互联网时代,尤其是是移动互联网时代,它已经大有作为。即使对于炙手可热的大数据、微服务等新型领域和理念而言,它也是一个相当重要的技术栈。甚至在即将爆发的人工智能和机器人大潮,我相信,Go语言也必会大放异彩!
作者介绍
郝林,Go语言北京用户组的发起人,极客学院Go语言课程顾问,著有图灵原创图书《Go并发编程实战》,同时也是在线免费教程《Go命令教程》和《Go语言第一课》的作者。目前在微赛时代任平台技术负责人。
参考文献
Go 1.6 Release Notes:https://tip.golang.org/doc/go1.6
Go 1.7 Release Notes:https://tip.golang.org/doc/go1.7
Go 1.8 Release Notes(Beta):https://tip.golang.org/doc/go1.8
The State of Go(2016):https://talks.golang.org/2016/state-of-go.slide