本文由InfoQ中文站授权转载,原文链接:
{http://www.infoq.com/cn/articles/2015-review-go?utm_source=infoq&utm_medium=popular_widget&utm_campaign=popular_content_list&utm_content=homepage }
现今,21世纪的第2个十年已经过半,互联网也真正进入了极速发展的阶段。在国内, 大家已经对“云计算”和“大数据”等名词耳熟能详了。在互联网软件开发领域,最主流或火爆的技术也无不与之有关。就拿Golang(也可称为Go语言)来说,它就号称“云计算时代的C语言”。Go语言在软件开发效率和运行效率之间做出了绝佳的权衡。这使得它既适应于互联网应用的快速开发,又能在高并发、高性能的开发场景中如鱼得水。在2015年,Go语言在服务端程序开发和Web开发领域大放光彩的同时也成功介入到了移动端开发领域。也正因为此,越来越多的创业公司(尤其是互联网应用和云计算领域的创业公司)选择Go语言作为其技术栈的重要组成部分。在国内,据不完全统计,已在生产环境中使用Go语言程序的知名互联网公司已有百度、美团、360、京东、搜狐、豌豆荚、宜信、微影时代 等等,更不用说国内日渐增多的云计算创业公司了。由此可见,对于广大的互联网软件开发者而言,关注和学习Go语言已经是已经很有必要的事情了。
回顾
2016年已经到来,距Google在2012年3月发布的Go 1.0已将近4年。在2015年,Go语言发生了不小的变化。从该年初发布的1.4版本到该年8月下旬发布的1.5版本,Go语言终于完成了自举的过程,即:几乎完全用Go语言程序重写了自己,仅留有少许汇编程序。Go语言的自举非常彻底,包括了最核心的编译器、链接器、运行时系统等。显然,这是一个很有意义的过程,代表着能力和自信。与此同时,Go语言的运行时性能得到了大幅提升,尤其是在1.5版本完成的并发GC使得Go语言程序在响应时间方面有了质的飞跃。另外,Go语言所支持的操作系统和计算架构越来越多,几乎涵盖了现今主流甚至非主流的所有选项。
当然,改变不止如此。下面,我们就列举几个比较惊艳的改变,并稍加剖析。
并发的GC
GC,一般认为是garbage collector的缩写形式,通常被译为垃圾回收器。不过,它有时候也被看做是garbage collection的缩写形式,中文译为垃圾回收。在下文中,当GC被当做动词用时,指的是垃圾回收。但当GC被当做名词用时,指的是垃圾回收器。
在1.4以及之前版本的Go语言中,每次GC都会导致完全的“stop the world”(也可称之为STW)。这意味着在GC期间,Go语言的运行时系统会让调度器暂停对已启用的Goroutine的一切调度。也就是说,任何未处于运行状态的Goroutine都不会被递交至内核线程和运行,直到当次GC完成。如此暂停的代价不容忽视,对于有高并发需求的程序来说有时会显得非常棘手。在1.5版本出来之前,Go语言的GC也常常因此被开发者们诟病。
Go 1.5的GC是一个非分代、无转移的采用标记-清扫算法和三色标记法的并发垃圾回收器。它大刀阔斧地利用各种手段大大缩减了STW的时间。Go语言官方保证,在50毫秒的Go程序运行时间中因GC导致的调度停顿至多只有10毫秒。有了这一保证,相当于为Go程序设定了一个响应时间的上限。对于对响应时间敏感的程序(许多互联网程序都是如此),这绝对是一个重大利好。当然,如此质的飞跃并不是一蹴而就的。实际上,Go 1.4也为此做了很多铺垫,比如对Goroutine栈的改造以完全保证GC的标记操作的准确无误。图1展示了在最近几个版本的Go语言中GC的STW时间与内存堆大小的对应关系。
图1
总之,Go 1.5为Go程序开启了全并发的时代。虽然Go语言官方说当前的GC还可以被进一步优化,但是笔者认为它已不再会成为Go程序性能的瓶颈。
自带标准工具的更新
Go语言的亮点之一就是自带了很多标准工具以帮助开发人员方便地进行Go程序的检查、格式化、编译、测试、部署,甚至升级。这些工具已经涵盖了一个软件的生命周期的方方面面,极大的方便了Go程序的开发者们。在1.4版本中,Go语言的标准工具集中加入了go generate。顾名思义,这是一个用于生成Go语言代码的命令。有意思的是,这源于一个几乎所有的计算机程序研发者们都有过的梦想——让计算机程序自己编写程序。go generate命令可以利用YACC(Yet Another Compiler Compiler,一种编译器的生成器)并根据某种描述文件来生成Go语言代码。不过,千万不要被这句话吓到。即使我们不懂YACC,甚至对Go语言的AST(Abstract Syntax Tree,译作抽象语法树)一无所知,也可以使用go generate命令。比如,我们可以利用go generate命令把一些HTML(Hypertext Markup Language,译作超文本标记语言)页面模板文件内置到生成的Go程序代码文件中(顺便说一句,Go语言有自己的HTML页面模板语法,可用于编写HTML页面模板)。这样就无需在部署用于Web站点的Go程序时携带那些额外的文件了。下面展示一小段用于实现此功能的代码:
程序员们应该可以猜到最后一行代码实际上是一行注释。实际上,只要有了这行注释(其中的tpl_loader是笔者写的一个小工具,也非常的简单易懂),再在当前代码包目录下运行go generate命令,就可以实现上述功能了。在Go语言官方博客中可看到更详细的说明。
在2015年里,Go语言在标准工具方面的增进还不止于此。一个更加令人兴奋的标准工具——go tool trace——随着1.5版本的发布而到来。Go程序开发者们可以利用这一工具来图形化的展示出Go程序的追踪文件。当然,在展示之前,我们先要通过某种方式生成这样的文件。Go语言为我们提供了三种用于生成Go程序追踪文件的方法:通过显式调用指定的标准库函数以手动生成、导入指定的标准库代码包以使其自动生成,以及在运行程序测试时添加指定标记来生成。此后,当我们运行go tool trace命令并把相应的可执行的Go程序文件和Go程序追踪文件作为参数以后,就可以在你的默认Web浏览器中看到类似下图的图形化展示了。
图2
如图所示,Go程序追踪文件可以呈现出对应Go程序的内存使用、Goroutine和内核线程状态、调度过程等信息。为我们调试Go并发程序提供了非常有力的辅助。
除了上述两个新增的标准工具之外,Go语言官方也对一些已有的标准工具做了改进,比如:为go build命令和go test命令新增了可用标记以使其更加灵活、增强了go tool vet命令和go doc命令的功能,等等。
访问控制的增强
Go语言对代码包的访问控制的进一步增强始于1.4版本。在1.4版本之前,Go语言对程序实体(包括变量、常量、类型声明、函数等)采取的是两级访问控制策略。程序实体的名称的第一个字母的大小写决定了它的可访问范围。若为大写,则该程序实体可以被存在于任何位置的代码访问到。我们称这样的程序实体是公开的。若为小写,则该程序实体仅能被存在于当前代码包的代码访问到。我们称这样的程序实体是包级私有的。这样简明扼要的规则非常容易被记住。
到了Go 1.4时代,第三种访问控制规则出现。官方称之为Internalpackages。其含义是,如果代码包A直接包含了一个名为internal的子代码包,那么这个internal包中公开的程序实体仅能被存在于代码包A及其子代码包中的代码访问到。这相当于使Go程序代码有了“模块级”的访问控制。
不过,在Go 1.4中,这个“模块级”的访问控制只是对Go语言源码和标准库中的(即<Go语言安装目录>/src中的)代码包有效,而对于其它代码包则是无效的。也就是说,这是一个过渡阶段。为的是让广大Go程序开发者有个适应期。到了Go 1.5,由internal代码包代表的“模块级私有”访问控制规则才对所有代码包有了强制约束力。至此,Go语言拥有了一套完整的三层访问控制策略。相比于之前的两层访问控制策略,它更加灵活和完善。
跨平台编译
在1.5版本之前,我们要想实现Go语言程序的跨平台编译是相当困难的。虽然因此催生出了几个开源的辅助工具,但其步骤也依然是相当繁琐的。其最主要的原因是那时的Go语言编译工具是由C语言编写的,是平台相关的。这里的平台相关,是指被编译后的程序的运行必要条件包含了目标计算机(也就是用于运行该程序的那台计算机)的操作系统和计算架构。其中,操作系统的可选项有windows、linux、darwin等,而计算架构的可选项目前有386(即32位计算架构)、amd64(即64位计算架构)和arm(一种基于精简指令集的计算架构,多用于便携设备专用CPU)。例如,我们在32位的Windows操作系统下,使用平台相关的Go语言编译工具编译的程序是不能在64位的Linux操作系统下运行的,反之亦然。
我们已经知道,Go 1.5的所有编译工具和运行时系统都被用Go语言重写了。这使得这些编译工具被进行了一系列合并,并且跨越了平台的界线。例如,之前针对不同计算架构的Go语言编辑器5g、6g和8g被合而为一并命名为compile。随之而来的优势就是,我们可以使用这些编译工具轻而易举的进行跨平台的程序编译操作。我们只需在程序编译操作之前设置一下目标计算的操作系统和计算架构。前者通过设置环境变量GOOS来实现,而后者通过设置环境变量GOARCH来实现。例如,当我在64位的Linux操作系统下通过执行如下命令来编译一个Go源码文件之后,就可以在32位的Windows操作系统下直接运行那个编译后的结果文件了。
对Android和iOS开发的支持
从Go 1.4开始,开发人员已经可以利用官方扩展库中的mobile代码包来编写Android App了。到了Go 1.5,其对iOS App的支持也已可用。这无疑是一大里程碑,使人振奋!它使得Go语言的适用领域大大拓展,并垒砌了其在移动互联网时代甚至物联网时代的根基。开发者们既可以使用Go语言来构建原生的Android App或iOS App,也可以用它来编写可被App程序调用的基础库。读者可以通过官方文档来了解相关步骤,也可以去看看Go语言的创始人之一Rob Pike为大家编写的示例App。另外,大家也可以关注Go语言北京用户组和北京GDG( Google Developer Group - Beijing)联合主办的Go语言技术聚会,其中会包括与此有关的Topic。此聚会的时间初定在2016年2月的下旬。
其他调整
除上述比较突出的变化之外,Go语言在很多地方也做了调整。比如,Goroutine内存栈的增长方式的变更,Goroutine内存栈的初始大小由8K缩减为了2K、GOMAXPROCS的默认值由1变成与当前计算机的CPU核心数一致、Go代码可以被用于生成动态链接库了,等等。对于这些调整,笔者就不一一细说了。不过,它们对于Go语言在2015年的精进也都起到了一定的推动作用。
总之,Go语言在2015年的发展迅速且振奋人心。无论在其本身的功能、性能和适用领域上,还是在社区方面(尤其是在中国)都是如此。如果说笔者在著《Go并发编程实战》这本书的时候还只是建议大家把Go语言作为自己的第一或第二编程语言并以此作为长线技术投资的话,那么现在我强烈建议所有互联网软件开发者都去尝试并使用Go语言构建他们的(个人或公司的)软件系统,并真正将其作为手边的常用工具。
Go语言号称是云计算时代的C语言。它也正在持续、快速地向着这一目标前进。如果你也打算跟上后云计算时代、物联网时代以及不久就会出现的人工智能时代的话,那么就很有必要玩儿转Go语言了。我相信,它一定不会让你失望。
展望
在2016年,我们目前可见的Go语言进展就是预计在2月份发布的1.6版本了。在这个版本中,Go语言官方准备重点发展UI库。这也是Go语言当前的一大短板。在官方的UI库完全就绪之前,笔者希望大家去关注一下七叶(优秀的国产Go语言IDE——LiteIDE——的作者)的新项目GoQt。此项目就是一个基于QT的Go语言UI库。鉴于LiteIDE的优秀,我非常看好这个项目。另外,Go语言在程序测试支持、程序运行分析以及程序调试方面都会有所改进,尤其是后者。实际上,许多不适应使用Go语言开发程序的程序员的最大抱怨就是Go语言程序不易调试(不过大家可以去了解下真正的Go语言爱好者是怎样调试Go程序的)。Go语言官方也在2015年的重大版本升级中对这一方面做出了很多改进。随之而来的就是Go语言在于各大主流IDE的集成方面所作出的不断努力。在Go 1.6,这种努力还会继续。程序的开发效率与运行效率同样重要。甚至在某些时候,前者比后者更加重要。这也是许多脚本语言得以生存并繁荣发展的重要原因之一。Go语言的创造者们更是深谙此道。最后,Go语言还会在移动App开发方面进行一步的增强。笔者相信Go语言在这一开发领域一定会有长足的进步的。
在奇点临近的当下,由于各种智能移动设备的先天优势,移动开发需求持续暴涨。我想,大多数互联网软件开发团队(尤其是创业团队)都会想用尽量精简的技术栈去支持多方产品的需求。如果能用一种既可快速上手又能高效运维的技术那该有多好。如果你通读本篇并有所思考,就很可能有与我相同的感受——Go语言就是这样的一门技术。如果你觉得理由并不充分或想深入了解Go语言,那么就加入到Go语言社区并切实的感受一下吧。笔者发起的Go语言北京用户组(微信公众号:golang-beijing)也欢迎你的加入。
在不久的将来,Go语言一定会实现在程序开发领域的全面覆盖。到那时,Go程序员的含金量也就毋庸置疑了。那么,我们为什么不现在就去做如此重要的技术投资或去积极的在内部培养Go开发工程师呢?
作者简介:郝林,Go语言北京用户组的发起人,著有图灵原创图书《Go并发编程实战》,同时也是在线免费教程《Go命令教程》和《Go语言第一课》的作者。现在微赛时代担任平台研发负责人。
有疑问加站长微信联系(非本文作者)