本文章创做于2020年4月,大约6000字,预计阅读时间15分钟,请坐和放宽。html
Go 是一个开源的编程语言,它能让构造简单、可靠且高效的软件变得容易[1]。Go 语言被设计成一门应用于搭载 Web 服务器,存储集群或相似用途的巨型中央服务器的系统编程语言。对于高性能分布式系统领域而言,Go语言无疑比大多数其它语言有着更高的开发效率。它提供了海量并行的支持,这对于游戏服务端的开发而言是再好不过了[1]。前端
其实早在2018年前,我就已经有在国内的程序员环境中断断续续地听到Go语言的消息,Go语言提供的方便的并发编程方式,十分适合我当时选择的毕业设计选题,可是受限于导师的语言选择、项目的进度追赶、考研的时间压榨,一直没有机会来好好地学习这门语言。java
在进入研究生阶段后,尽管研究的方向和算法相关,但将来的职业方向仍是选择了之后端为主,主要是由于想作更多和业务相关的工做。为了能在有限的时间里给予本身足够深的知识底蕴,选择了一些让本身去深刻了解的方向,Go语言天然也在其中,今天终于有机会来开始研究这门语言。git
撰写此文的初衷,是本文的标题,也是我做为初学者一直以来的疑问:程序员
“我为何要用Go语言?”github
为了回答这个问题,我翻阅了不少Go语言相关的文档、书籍和教程,我发现我很难在它们之中找到很是明显直接的答案,书上和教程只会说,“是的,Go语言好用”。golang
对于部分人来讲,这个问题的答案或许很“明显”,好比选择Go语言是由于Google设计的语言、Go开发赚的钱多、XX公司使用Go语言等等,若是想要了解这门语言更加本质的东西,仅仅这些答案我认为是还不够的。算法
部分Go的教徒可能会说,他们选择的理由是和语言自己相关的,好比:编程
的确,Go是有这些特色,但这并不是都是Go独有的:后端
一些Go的忠实粉丝把这种All in One的特性做为评价语言的标准,他们认为至少在这些方面,Go是能够完美的代替其余语言的。
那么,Go真的能优秀到彻底替代另外一个语言么?
其实未必,我始终认为银弹是不存在的[2],不管是在此次调查前,仍是在此次调查后。
本文从Go语言被设计的初衷出发,深刻互联网各类角落,调查Go所具备的那些特性是否足够优秀,同时和其余语言进行适当的比较,你能够选择性的阅读、接受或者反对个人内容,毕竟有交流才能传播知识。
个人最终目的是让更多的初学者看到Go没有轻易暴露出的缺点,同时也能看到Go真正优秀的地方。
Go语言的主要目标是将静态语言的安全性和高效性与动态语言的易开发性进行有机结合,达到完美平衡,从而使编程变得更加有乐趣,而不是在艰难抉择中痛苦前行[3]。
Google公司不可能平白无故地设计一个新语言(一些特性相比于其余语言也没有新到哪里去),这一切确定是有缘由的。
设计Go语言是为了解决当时Google开发遇到的一些问题[4]:
找不到什么合适的语言,想着反正都是弄来本身用,Google选择造个轮子试试。
Go 语言起源 2007 年,并于 2009 年正式对外发布。它从 2009 年 9 月 21 日开始做为谷歌公司 20%兼职项目,即相关员工利用 20% 的空余时间来参与 Go 语言的研发工做。该项目的三位领导者均是著名的 IT 工程师:Robert Griesemer,参与开发 Java HotSpot 虚拟机;Rob Pike,Go 语言项目总负责人,贝尔实验室 Unix 团队成员,参与的项目包括 Plan 9,Inferno 操做系统和 Limbo 编程语言;Ken Thompson,贝尔实验室 Unix 团队成员,C 语言、Unix 和 Plan 9 的创始人之一,与 Rob Pike 共同开发了 UTF-8 字符集规范。自 2008 年 1 月起,Ken Thompson 就开始研发一款以 C 语言为目标结果的编译器来拓展 Go 语言的设计思想[3]。
Go 语言设计者:Griesemer、Thompson 和 Pike [3]
当时Google的不少工程师是用的都是C/C++,因此语法的设计上接近于C,Go的设计师们想要解决其余语言使用中的缺点,可是仍保留他们的优势[5]:
emmm,这些听起来仍是比较玄乎,毕竟设计归设计,实现归实现,咱们回顾一下如今Go的几个主要特色,编译速度、执行速度、内存管理以及并发编程。
固然,设计Go语言也不是彻底从零开始,最初Go的团队尝试设计实现一个Go语言的编译前端,由基于C的gcc编译器来编译成机器代码,这个面向gcc的前端编译器也就是目前的Go编译器之一的gccgo。
与其说Go的编译为何快,不如先说说C++的编译为何慢,C++也能够用gcc编译,编译速度的大部分差别颇有可能来源于语言设计自己。
在讨论问题以前,其中须要先说明的一点是:这里比较的编译速度都是在静态编译下的。
静态编译和动态编译的区别:
两种方式有各自的优势和缺点,前者不须要去管理不一样版本库的兼容性问题,后者能够减小内存和存储的占用(由于可让不一样程序共享同一个库),两种方式孰优孰弱,要对应到具体的工程问题上,Go默认的编译方式是静态编译。
回到咱们要讨论的问题:C++的编译为何慢?
C++编译慢的主要两个大头缘由[6]:
C++使用include方式引用头文件,会让须要编译的代码有乘数级的增长,例如当同一个头文件被同一个项目下的N个文件include时,编译器会将头文件引入到每一份代码中,因此同一个头文件会被编译N次(这在大多数时候都是没必要要的);C++使用的模板是为了支持泛型编程,在编写对不一样类型的泛型函数时,能够提供很大的便利,可是这对于编译器来讲,会增长很是多没必要要的编译负担。
固然C++对这两个问题有不少后续的优化方法,可是这对于不少开发者来讲,他们不想在这上面有过多时间和精力开销。
大部分后来的编程语言在引入文件的方式上,使用了import module来代替include 头文件的方式,import解决了重复编译的问题,固然Go也是使用的import方式;在模板的编译问题上,因为Go在设计理念上遵循从简入手,因此没有将泛函编程归入到设计框架中,因此天生的没有模版编译带来的时间开销(没有泛型支持也是不少人不满Go语言的理由)。
在Go 的1.5 版本中,Go团队使用Go语言来编写Go语言的编译器(也叫自举),相比于gccgo来讲:
在此以外,Go语言语法中的关键字也是很是少的(Go1.11版本里只有25个)[7],这也能够减小编译器花费在语法解析上的时间开销。
因此在我看来,Go编译速度快,主要出于四个缘由:
因此为了加快编译速度、放弃C++而转入Go的同时,也要考虑一下是否要放弃泛型编程的优势。
注:泛型可能在Go 2版本得到支持。
Go的执行速度,能够参考一个语言性能测试数据网站 —— The Computer Language Benchmarks Game[8]。
这个网站在不一样的算法上对每一个语言进行测试,而后给出时间和内存上的开销数据比对。
比较的语言有C++、Java、Python。
首先是时间开销:
注意:时间开销的单位是s,而且Y轴为了方便进行不一样跨度上的比较,因此选取的是对数轴(即非线性轴,为1-10-100-1000的比较跨度)。
而后是内存开销:
注意:Y轴为了方便进行不一样跨度上的比较,因此选取的是对数轴(即非线性轴,为1000-10000-100000-1000000的比较跨度)。
须要注意的是,语言自己的性能只决定了一个程序的最高理论性能,程序具体的性能还要取决于这个程序的实现方法,因此当各个语言的性能并无太大的差别时,性能每每只取决于程序实现的方式。
经过两个图的数据能够分析:
Go的并发之因此比较受欢迎,网络上的不少内容集中在几个方面:
因为Go在设计的时候就考虑到了并发的支持,或者说不少特性都是为了并发而设计,这和一些后期库支持并发和第三方库支持并发的语言不一样。
因此Go的并发到底有多方便?在Go中使用并发,只须要在普通的函数执行前加上一个go关键字,就能够新建一个线程让函数在其中执行:
func main() { go loop() // 启动一个goroutine loop() }
这样带来的好处不只仅是让并发编程更方便了,在一些特定状况下,好比Go引用一些使用了并发的库时,这些库所使用的并发也是基于Go自己的并发设计,不会存在库使用另外一套并发实现的状况,这样Go调度器在处理程序中的各类并发线程时,能够有更加统一化的管理方式。
不过Go的并发对于程序的实现要求仍是比较高的,在使用一些通讯Channel的场合,稍有疏忽就可能出现死锁的问题,好比:
fatal error: all goroutines are asleep - deadlock!
Go的并发量能够比大部分语言里普通的线程实现要高,这受益于轻量级的Goroutine,轻量化主要是它所占用的空间要小得多,例如64位环境下的JVM,它会默认固定为每一个线程分配1MB的线程栈空间,而Goroutines大概只有4-8KB,以后再按需分配。足够轻量化的线程在相同的内存下也就能够有更高并发量(服务器CPU尚未饱和的状况下),同时也能够减小不少上下文切换的时间开销[9]。可是若是你的每一个线程占用空间都很是大时(好比10MB,固然这是很是规需求的状况下),Go的轻量化优点就没有那么明显了。
Go在并发上的优势很明显,也是Go的功能目标,从语言设计上支持了并发,提供了统一便捷的工具,复杂的并发业务也须要在Go的一整套并发规范体系下进行编程,固然这确定会牺牲部分实现自由度,但能够得到性能的提升和维护成本的降低。
PS:关于Go调度器的内容在这里并无被说起,由于很难用简单的文字向读者说明该调度方式和其余调度方式的优劣,将在将来的某一篇中会细致地介绍Go调度器的内容。
垃圾回收(英语:Garbage Collection,缩写为GC),在计算机科学中是一种自动的存储器管理机制。当一个计算机上的动态存储器再也不须要时,就应该予以释放,以让出存储器,这种存储器资源管理,称为垃圾回收。垃圾回收器可让程序员减轻许多负担,也减小程序员犯错的机会[10]。
在使用Go或者其余支持GC的语言时,不用再像C++同样,手动地去释放不须要的变量占用的内容空间(free/delete)。
的确,这很方便(对于懒人和容易忘记主动释放的人),可是也多了一些限制(暗箱操做的不透明性以及在GC处理上的性能开销)。GC也不是万能的,当遇到一些对性能要求较高的场景,仍是须要记得进行一些主动释放或优化操做(好比说自定义内存池)。
PS:将在将来的某一篇中会细致地介绍Go垃圾回收的细节(若是大家也以为有必要的话)。
Go有不少优势,编译快、性能好、天生并发以及垃圾回收,不少比较有特点的内容也尚未说到(好比gofmt)。
Go语言也有不少缺点,好比第三方库支持还不够多(相比于Python来讲就少的太多了)、支持编译的平台还不够广、还有被称为噩梦的依赖版本管理(已经在改善了,可是尚未达到彻底可靠的程度)。
因此到底Go适合作什么,不适合作什么?
分析了这么多后,这个问题其实很难回答,但咱们能够选择先从不适合的领域把Go剔除掉,看看咱们会剩下什么。
你能够找到相似上面那样的不少场景,你可能会发现Go并不能那么完美地替代掉谁。
最后,到了咱们的终极问题,Go到底适合作什么?
读到这里你可能会以为,好像是我把Go的特性吹了一遍,而后忽然告诉你可能Go不适合你。
Go天生并发,面向并发,因此Go的定位一直很清楚,从最浅显的视角来看,至少Go做为一个有较高性能的并发后端来讲,是具备很是大的诱惑力的。
尤为对于后端相关的程序员而言,在某些业务功能的初步实现上,简洁的语法、内置的并发、快速的编译,均可以让你更加高效快速地完成任务(前提是Go的内容足以完成你的任务),不用再去担心编译优化和内存回收、不用担忧过多的时间和内存开销、不用担忧不一样版本库之间的冲突(静态编译)以及不用担忧交叉编译平台适配问题。
大部分状况下,编写一个服务,你只须要:实现、编译、部署、运行。
高效快速,足够敏捷,这在企业的绝大部分项目的初期都是适用的,这也是大部分项目对开发初期的要求。当一个项目或者服务真的能够发展下去,需求的确触碰到Go的天花板时,再考虑使用更加好的语言或方法去优化也为时不晚。
简而言之,尽管Go的过于简洁带来了不少问题(有些人说的难听点叫过于简单),Go所具备的优势,可让大部分人用编程语言这种工具,来解决对他们而言更加剧要的问题。
Go语言不是银弹,但它的确能有效地解决这些问题。
在调查Go的过程当中,发现了一些比较有意思、或者比较实用的文章,一并附在这里。