# 语法简单

抛开语法样式不谈,单就类型和规则而言,Go与C99、C11相似之处颇多,这也是我能接受它被冠以“NextC”名号的重要原因。

即便我是个坚定的C拥趸,也不得不承认,它处于简单和复杂的两极。C简单到你每写下一行代码,都能在脑中想象出编译后的模样,指令如何执行,内存如何分配,等等。而C的复杂在于,它有太多隐晦而不着边际的规则,着实让人头疼。相比较而言,Go从零开始,没有历史包袱,在汲取众多经验教训后,可从头规划一个规则严谨、条理简单的世界。

人们习惯拿关键字和控制语句的数量来作为Go简单的例证,我倒觉着这并不合适。诚然,更少的语言规则有助于入门学习,这无可厚非。但更重要的在于,语言规则严谨,没有歧义,更没什么黑魔法变异用法。任何人写出的代码都基本一致,这才是简单的本质。放弃部分“灵活”和“自由”,换来更好的维护性,我觉得是值得的。

将“++”、“--”从运算符降级为语句,保留指针,但默认阻止指针运算。初时的不习惯,并不能掩盖它们带来的长期的好处。还有,将切片和字典作为内置类型,从运行时的层面进行优化,这也算是一种“简单”。

# 并发模型

时至今日,并发编程已成为程序员的基本技能,在各个技术社区都能看到诸多与之相关的讨论主题。究竟哪种方式是最佳并发编程体验,或许会一直争论下去。但Go却一反常态做了件极大胆的事,从根子上将一切都并发化,运行时用Goroutine运行所有的一切,包括main.main入口函数。

可以说,Goroutine是Go最显著的特征。它用类协程的方式来处理并发单元,却又在运行时层面做了更深度的优化处理。这使得语法上的并发编程变得极为容易,无须处理回调,无须关注执行绪切换,仅一个关键字,简单而自然。

搭配channel,实现CSP模型。将并发单元间的数据耦合拆解开来,各司其职,这对所有纠结于内存共享、锁粒度的开发人员都是一个可期盼的解脱。若说有所不足,那就是应该有个更大的计划,将通信从进程内拓展到进程外,实现真正意义上的分布式。

# 内存分配

将一切并发化固然是好,但带来的问题同样很多。如何实现高并发下的内存分配和管理就是个难题。好在Go选择了tcmalloc,它本就是为并发而设计的高性能内存分配组件。

可以说,内存分配器是运行时三大组件里变化最少的部分。刨去因配合垃圾回收器而修改的内容,内存分配器完整保留了tcmalloc的原始架构。使用cache为当前执行线程提供无锁分配,多个central在不同线程间平衡内存单元复用。在更高层次里,heap则管理着大块内存,用以切分成不同等级的复用内存块。快速分配和二级内存平衡机制,让内存分配器能优秀地完成高压力下的内存管理任务。

在最近几个版本中,编译器优化卓有成效。它会竭力将对象分配在栈上,以降低垃圾回收压力,减少管理消耗,提升执行性能。可以说,除偶尔因性能问题而被迫采用对象池和自主内存管理外,我们基本无须参与内存管理操作。

# 垃圾回收

垃圾回收一直是个难题。早年间,Java就因垃圾回收低效被嘲笑了许久,后来Sun连续收纳了好多人和技术才发展到今天。可即便如此,在Hadoop等大内存应用场景下,垃圾回收依旧捉襟见肘、步履维艰。

相比Java,Go面临的困难要更多。因指针的存在,所以回收内存不能做收缩处理。幸好,指针运算被阻止,否则要做到精确回收都难。

每次升级,垃圾回收器必然是核心组件里修改最多的部分。从并发清理,到降低STW时间,直到Go的1.5版本实现并发标记,逐步引入三色标记和写屏障等等,都是为了能让垃圾回收在不影响用户逻辑的情况下更好地工作。尽管有了努力,当前版本的垃圾回收算法也只能说堪用,离好用尚有不少距离。可对一个从R60一路跟踪源码走过来的程序员而言,我目睹了Go Team为此所付出的全部努力。我在此表达敬意,以及对未来某个飞跃的预期。

# 静态链接

Go刚发布时,静态链接被当作优点宣传。只须编译后的一个可执行文件,无须附加任何东西就能部署。这似乎很不错,只是后来风气变了。连着几个版本,编译器都在完善动态库buildmode功能,场面一时变得有些尴尬。

暂不说未完工的buildmode模式,静态编译的好处显而易见。将运行时、依赖库直接打包到可执行文件内部,简化了部署和发布操作,无须事先安装运行环境和下载诸多第三方库。这种简单方式对于编写系统软件有着极大好处,因为库依赖一直都是个麻烦。事实上,我们也能看到越来越多的工具采用Go开发,其中恐怕就有此等原因。

# 标准库

学习编程语言,早已不是学一点语法规则那么简单。现在更习惯称作选择Ecosystem(生态圈),而这其中标准库的作用和分量尤为明显。

功能完善、质量可靠的标准库为编程语言提供了充足动力。在不借助第三方扩展的情况下,就可完成大部分基础功能开发,这大大降低了学习和使用成本。最关键的是,标准库有升级和修复保障,还能从运行时获得深层次优化的便利,这是第三方库所不具备的。

Go标准库虽称不得完全覆盖,但也算极为丰富。其中值得称道的是net/http,仅须简单几条语句就能实现一个高性能Web Server,这从来都是宣传的亮点。更何况大批基于此的优秀第三方Framework更是将Go推到Web/Microservice开发标准之一的位置。

当然,优秀第三方资源也是语言生态圈的重要组成部分。近年来崛起的几门语言中,Go算是独树一帜,大批优秀作品频繁涌现,这也给我们学习Go提供了很好的参照。

# 工具链

完整工具链对于日常开发极为重要。Go在此做得相当不错,无论是编译、格式化、错误检查、帮助文档,还是第三方包下载、更新都有对应工具。其功能未必完善,但起码算得上简单易用。

内置完整测试框架,其中包括单元测试、性能测试、代码覆盖率、数据竞争,以及用来调优的pprof,这些都是保障代码能正确而稳定运行的必备利器。

除此之外,还可通过环境变量输出运行时监控信息,尤其是垃圾回收和并发调度跟踪,可进一步帮助我们改进算法,获得更佳的运行期表现。

遗憾的是,发展6年的Go依然缺少一个真正意义上的调试器,对此我个人颇有怨念。另外,依赖包管理也是社区争论的焦点之一。

Last Updated: 2/7/2022, 11:17:10 PM