“AI 技术+人才”如何成为企业增长新引擎?戳此了解>>> 了解详情
写点什么

解读 2016 之 Golang 篇:极速提升,逐步超越

  • 2016-12-15
  • 本文字数:6146 字

    阅读完需:约 20 分钟

Go 语言已经 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 年做出的那些大动作。你可以对比我之前写的《解读2015 之Golang 篇:Golang 的全迸发时代》来看。

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 协议的通讯过程的详细信息。

复制代码
package main
import (
"context"
"fmt"
"log"
"net/http"
"net/http/httptrace"
"os"
)
func main() {
traceCtx := httptrace.WithClientTrace(context.Background(), &httptrace.ClientTrace{
GetConn: func(hostPort string) {
fmt.Printf("Prepare to get a connection for %s.\n", hostPort)
},
GotConn: func(info httptrace.GotConnInfo) {
fmt.Printf("Got a connection: reused: %v, from the idle pool: %v.\n",
info.Reused, info.WasIdle)
},
PutIdleConn: func(err error) {
if err == nil {
fmt.Println("Put a connection to the idle pool: ok.")
} else {
fmt.Println("Put a connection to the idle pool:", err.Error())
}
},
ConnectStart: func(network, addr string) {
fmt.Printf("Dialing... (%s:%s).\n", network, addr)
},
ConnectDone: func(network, addr string, err error) {
if err == nil {
fmt.Printf("Dial is done. (%s:%s)\n", network, addr)
} else {
fmt.Printf("Dial is done with error: %s. (%s:%s)\n", err, network, addr)
}
},
WroteRequest: func(info httptrace.WroteRequestInfo) {
if info.Err == nil {
fmt.Println("Wrote a request: ok.")
} else {
fmt.Println("Wrote a request:", info.Err.Error())
}
},
GotFirstResponseByte: func() {
fmt.Println("Got the first response byte.")
},
})
req, err := http.NewRequest("GET", "http://www.golang.org/", nil)
if err != nil {
log.Fatal("Fatal error:", err)
}
req = req.WithContext(traceCtx)
_, err = http.DefaultClient.Do(req)
if err != nil {
fmt.Fprintf(os.Stderr, "Request error: %v\n", err)
os.Exit(1)
}
}

强烈建议你动手运行一下这个小程序,享受一下掌控全局的感觉。

1.4 子测试

Go 1.7 中增加了对子测试( https://blog.golang.org/subtests)的支持,包括功能测试和性能测试。子测试的主要目的是在测试函数中区分和展示因不同的测试参数或测试数据带来的不同的测试结果。请看下面的测试程序。

复制代码
package subtest
import (
"fmt"
"math/rand"
"strconv"
"testing"
)
// KE 代表键 - 元素对。
type KE struct {
key string
element int
}
// BenchmarkMapPut 用于对字典的添加和修改操作进行测试。
func BenchmarkMapPut(b *testing.B) {
max := 5
var kes []KE
for i := 0; i <= max; i++ {
kes = append(kes, KE{strconv.Itoa(i), rand.Intn(1000000)})
}
m := make(map[string]int)
b.ResetTimer()
for _, ke := range kes {
k, e := ke.key, ke.element
b.Run(fmt.Sprintf("Key: %s, Element: %#v", k, e), func(b *testing.B) {
for i := 0; i < b.N; i++ {
m[k] = e + i
}
})
}
}

在程序所在目录下使用 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
ok _/Users/haolin/infoq-2016_review_go /demo/subtest 8.678s

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 其他值得一提的改进

1.6.1 检测并报告对字典的非并发安全访问

从 Go 1.6 开始,Go 运行时系统对字典的非并发安全访问采取零容忍的态度。请看下面的程序。

复制代码
package main
import "sync"
func main() {
const workers = 100
var wg sync.WaitGroup
wg.Add(workers)
m := map[int]int{}
for i := 1; i <= workers; i++ {
go func(i int) {
for j := 0; j < i; j++ {
m[i]++
}
wg.Done()
}(i)
}
wg.Wait()
}

该程序在未施加任何保护的情况下在多个 Goroutine 中并发地访问了字典实例 m。我们知道,Go 原生的字典类型是非并发安全的。所以上面这样做很可能会让 m 的值产生不可预期的变化。这在并发程序中应该坚决避免。在 1.6 之前,如此操作的 Go 程序并不会因此崩溃。但是在 1.6,运行上述程序后就立刻会得到程序崩溃的结果。Go 运行时系统只要检测到类似代码,就会强制结束程序并报告错误。

1.6.2 sort 包的性能提升

Go 语言团队一直致力于标准库中众多 API 的性能提升,并且效果向来显著。我把 sort 包单拎出来强调是因为 sort.Sort 函数因性能优化而在行为上稍有调整。在 Go 1.6,sort.Sort 函数减少了大约 10% 的比较操作和交换操作的次数,从而获得了 20%~50% 的性能提升。不过,这里有一个副作用,那就是 sort.Sort 函数的执行会使排序算法不稳定。所谓不稳定的排序算法,就是排序可能会使排序因子相等的多个元素在顺序上不确定。比如,有如下需要根据长度排序的字符串的切片:

var langs= []string{“golang”, “erlang”, “java”, “python”, “php”, “c++”, “perl”}

经 sort.Sort 函数排序后,该切片只几个长度相等的元素 golang、erlang 和 python 的先后顺序可能就不是这样了,可能会变成 erlang、golang、python。虽然它依然会依据排序因子(这里是字符串长度)进行完全正确的排序,但是如此确实可能对一些程序造成影响。

如果你需要稳定的排序,可以使用 sort.Stable 函数取而代之。

1.6.3 context 包进入标准库

在 Go 1.7 发布时,标准库中已经出现了一个名为 context 的代码包。该代码包原先的导入路径为 golang.org/x/context,而后者现在已经不存在了。context 包被正式引入标准库,并且标准库中的很多 API 都因此而做了改变。context.Context 类型的值可以协调多个 Groutine 中的代码执行“取消”操作,并且可以存储键值对。最重要的是它是并发安全的。与它协作的 API 都可以由外部控制执行“取消”操作,比如:取消一个 HTTP 请求的执行。

1.6.4 go tool trace 的增强

go tool trace 自 Go 1.5 正式加入以来,成为了 Go 程序调试的又一利器。到了 Go 1.7,它已经得到了大幅增强。比如,执行时间的缩短、跟踪信息的丰富,等等。

1.6.5 unicode 包现基于 Unicode 9.0/h4>

Go 1.7 升级了 unicode 包,使它支持 Unicode 9.0 标准。在这之前,它支持的 Unicode 8.0 标准。

1.6.6 新的编译器后端——SSA

SSA 作为新的编译器后端,可以让编译器生成压缩比和执行效率都更高的代码,并为今后的进一步优化提供了更有力的支持。在性能方面,它可以让程序减少 5% 至 35% 的 CPU 使用时间。

到这里,我向大家展示了 Go 语言在 2016 年的一些显著变化。由于篇幅原因,还有很多 Go 运行时系统和标准库的改动没能提及。尤其是性能方面的改进一直在持续,并潜移默化地为广大 Go 程序员提供着底层红利。

我强烈建议所有 Go 程序员紧跟 Go 语言团队的脚步,升级版本,享受红利。

2. 展望

2.1 新版本

关于展望,莫过于广大 Go 程序员翘首期盼的 Go 1.8 了。这里提一下几个如甘霖般的特性。

  1. Go 编写的 HTTP 服务器支持平滑地关闭。这一功能早已由很多第三方代码包实现,但是这次官方终于给出了答案。
  2. 支持 HTTP/2 的 Server Push。这个就不多说了,肯定会比 Hijack 更好用。
  3. 新增了 plugin 包,你可以把一些 Go 程序作为插件动态地加载进你的程序了。
  4. 更广泛的上下文支持,自从标准库中有了 context 包,它就在很多地方起作用了。很多基于标准库的接口功能都可以执行“取消”操作。在 Go 1.8 中,范围将进一步扩大,比如:database/sql 包和 testing 包都对上下文进行了支持。
  5. sort 包的功能改进,对于切片,我们不用再为了使用它的排序功能去编写某个接口的实现类型。
  6. go test 命令有了新的标记:-mutexprofile。该标记用于提供关于锁争用的概要文件。
  7. 当然,最值得期待的仍然是 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 语言第一课》的作者。目前在微赛时代任平台技术负责人。

参考文献

  1. Go 1.6 Release Notes: https://tip.golang.org/doc/go1.6
  2. Go 1.7 Release Notes: https://tip.golang.org/doc/go1.7
  3. Go 1.8 Release Notes(Beta): https://tip.golang.org/doc/go1.8
  4. The State of Go(2016): https://talks.golang.org/2016/state-of-go.slide
2016-12-15 16:0213295
用户头像

发布了 22 篇内容, 共 17.4 次阅读, 收获喜欢 85 次。

关注

评论

发布
暂无评论
发现更多内容

搭建企业私有GIT服务

IT视界

git

架构实战营 - 模块 6- 作业

carl

虚拟货币监管再加码:央行约谈部分金融机构 要求切断支付链路

CECBC

奇亚节点分币系统搭建,Bzz节点分币APP搭建

“半监督”、“自监督”怎么用?| 算法深度剖析与实战分享

网易易盾技术团队

AI 算法 算法实践 实践案例 深度半监督

5分钟速读之Rust权威指南(二十八)RefCell<T>

wzx

rust

从渗透测试小白到网络安全大佬的成长之路

学神来啦

Linux 运维 网络安全 渗透测试

鉴释×CSDN丨国内外操作系统生态差异在哪?

鉴释

操作系统

中国政府大数据市场,我们又是第一

浪潮云

云计算

双非渣硕,开发两年,苦刷算法47天,四面字节斩获offer

Java 程序员 架构 面试 算法

🌏【架构师指南】分布式技术知识点总结(中)

洛神灬殇

分布式架构 架构师技能 分布式技术 6月日更

微服务到底是什么?spring cloud在国内中小型公司能用起来吗?

Java架构师迁哥

算法有救了!GitHub上神仙项目手把手带你刷算法,Star数已破110k

Java架构师迁哥

强化学习 | COMA

行者AI

人工智能

Java的函数式接口

中原银行

Java 函数式接口 中原银行

区块链如何赋能智慧城市

CECBC

产业互联网时代的数字化转型与创新

CECBC

冷门科普类自媒体如何才能脱颖而出

石头IT视角

新华三商用终端新品全系入市,重塑办公极致体验

科技热闻

ES6 迭代器简述

编程三昧

JavaScript 大前端 ES6 迭代器

网络抓包实战04——深入浅出连接建立

青春不可负,生活不可欺

指挥中心情指勤一体化解决方案,河北公安情指勤一体化建设

从零开始学习3D可视化之物体选择

ThingJS数字孪生引擎

大前端 可视化 程序媛 3D可视化 数字孪生

浅谈B端产品的表单元素设计

LigaAI

产品经理 UI 产品设计与思考

英特尔宋继强:异构计算的关键一环,先进封装已经走向前台

E科讯

网络抓包实战05——深入浅出连接关闭

青春不可负,生活不可欺

年中面试经历:美团2面+字节3面+阿里4面+腾讯Java面经,终入字节

Java 程序员 架构 面试

联邦学习—金融数据壁垒和隐私保护的解决之道

索信达控股

大数据 金融科技 联邦学习 金融 数据隐私

接口全面重构TypeScript ,让uni-app 具备出色的基础音视频能力

ZEGO即构

typescript uni-app 音视频

网络抓包实战03——TCP/IP协议栈:数据包如何穿越各层协议

青春不可负,生活不可欺

dubbogo 社区负责人于雨说

apache/dubbo-go

dubbo dubbo-go dubbogo

解读2016之Golang篇:极速提升,逐步超越_语言 & 开发_郝林_InfoQ精选文章