当前位置: 首页 > 科技观察

为什么要使用Go语言?

时间:2023-03-15 10:08:45 科技观察

前言Go是一种开源编程语言,可以轻松构建简单、可靠、高效的软件[1]。Go语言被设计为一种系统编程语言,用于托管Web服务器、存储集群等的巨型中央服务器。对于高性能分布式系统领域,Go语言无疑比其他大多数语言具有更高的开发效率。它提供海量并行支持,非常适合游戏服务器开发[1]。其实早在2018年,我就在国内的程序员环境中断断续续地听说过Go语言的消息。Go语言提供的便捷的并发编程方式,非常适合我当时选择的毕业设计题目,但是受了它的影响。受限于导师的语言选择,项目的进度,考研的时间紧迫,我一直没有机会学好这门语言。进入研究生阶段后,虽然研究方向是算法相关,但未来的职业方向还是选择了后端,主要是想多做一些业务相关的工作。为了在有限的时间内给自己一个足够深的知识基础,我选择了一些方向让自己深入了解,Go语言自然也在其中。今天终于有机会开始研究这门语言了。为什么要使用Go语言?写这篇文章的初衷就是本文的标题,也是我作为初学者一直有的疑问:“我为什么要使用Go语言?”为了回答这个问题,我看了很多Go语言相关的文档、书籍和教程,我发现很难从中找到非常明显和直接的答案,而书籍和教程只是说,“是的,Go语言简单易用”。对于某些人来说,这个问题的答案可能是“显而易见的”。比如选择Go语言是因为Google设计的语言,Go开发很赚钱,XX公司用Go语言等等,如果你想了解这门语言更本质的东西,我觉得光靠这些答案就够了不够。有的Go信徒可能会说,他们选择的原因与语言本身有关,例如:Go编译快Go执行快Go并发编程方便Go有垃圾收集(GarbageCollection,GC)的确,Go有这些特点,但是This不全是Go特有的:运行时解释型脚本语言(如Python)几乎不需要时间来编译C、C++,甚至汇编,并且基本上可以压榨出机器的大部分性能大多数语言都有并发编程大多数语言的支持库不需要程序员主动关注内存情况。一些Go的忠实粉丝将这种AllinOne特性视为评估语言的标准。他们认为至少在这些方面,Go可以完美地替代其他语言。.那么,Go真的可以完全取代另一种语言吗?其实也未必,我一直认为没有灵丹妙药[2],无论是在这次调查之前还是之后。本文从Go语言设计的初衷出发,深入互联网各个角落,考察Go的特性是否足够好,并与其他语言进行适当的比较。你可以有选择地阅读,接受或反对我的内容,毕竟传播知识是有交流的。我的最终目的是让更多的初学者看到Go不易暴露的缺点,同时看到Go真正好的地方。设计Go的初衷Go语言的主要目标是将静态语言的安全高效与动态语言的易开发性有机地结合起来,达到完美的平衡,让编程变得更有趣,而不是比在困难的选择中痛苦继续前进[3]。谷歌不可能无缘无故地设计一门新语言(有些特性与其他语言相比并不新鲜),这一切肯定是有原因的。Go语言旨在解决当时Google开发遇到的一些问题[4]:C++编译慢,没有现代(入门级友好)内存管理数万行代码,难以维护和部署各种平台,交叉编译困难。。。一直找不到合适的语言,心想反正自己用,于是谷歌选择造轮子试试。Go语言起源于2007年,2009年正式发布,从2009年9月21日开始作为谷歌20%的兼职项目,即相关员工利用20%的业余时间参与研发的围棋语言。该项目的三位负责人均为知名IT工程师:RobertGriesemer,曾参与JavaHotSpot虚拟机的开发;RobPike,Go语言项目总负责人,贝尔实验室Unix团队成员,参与项目包括Plan9、InfernoOperationsSystems和Limbo编程语言;KenThompson是贝尔实验室Unix团队的成员,C语言、Unix和Plan9的联合创始人,与RobPike共同开发了UTF-8字符集规范。从2008年1月开始,KenThompson一直在开发以C语言为目标结果的编译器,以扩展Go语言的设计思想[3]。当时Google的很多工程师都用C/C++,所以语法设计接近C。Go的设计者想解决其他语言的缺点,但仍保留其优点[5]:静态类型和运行时效率,可读性以及易用性、高性能网络和多进程……emmm,这些听起来有点神秘。毕竟设计归设计,实施归实施。现在让我们回顾一下Go的一些主要特性。编译速度、执行速度、内存管理、并发编程。为什么Go的编译速度快?当然,Go语言的设计并不是完全从零开始。起初,Go团队尝试设计并实现了一个Go语言编译前端,由基于C的gcc编译器编译成机器码。这个面向gcc的前端编译器就是gccgo,目前的Go编译器之一。与其说Go编译为什么快,不如先说说C++编译为什么慢。C++也可以用gcc编译。编译速度的大部分差异很可能来自语言设计本身。在讨论问题之前,需要说明的一点是:这里比较的编译速度都是静态编译下的。静态编译和动态编译的区别:静态编译:编译器在编译一个可执行文件时,将使用的链接库提取出来,并将链接打包到可执行文件中。编译结果只有一个可执行文件。动态编译:可执行文件需要附带一个独立的库文件。该库不打包到可执行文件中以减小可执行文件的大小,并且可以在执行过程中调用该库。这两种方法各有优缺点。前者不需要管理不同版本库的兼容性问题,后者可以减少内存和存储的使用(因为不同的程序可以共享同一个库)。这两种方式哪种Weak更好,要对应具体的工程问题,Go默认的编译方式是静态编译。回到我们要讨论的问题:为什么C++编译慢?C++编译慢主要有两个原因[6]:以头文件include方式编译模板C++使用include方式引用头文件,这会使需要编译的代码倍数增加,比如同一个头文件被同一个工程下的N个文件包含时,编译器会在每段代码中引入这个头文件,所以同一个头文件会被编译N次(这在大型项目中是没有必要的)时间);C++使用的模板是为了支持泛型编程,在为不同类型编写泛型函数时可以提供极大的方便,但是这会给编译器增加很多不必要的编译负担。当然,C++针对这两个问题有很多后续的优化方法,但是对于很多开发者来说,并不想在上面花费太多的时间和精力。后来的编程语言大多在引入文件的方式上使用import模块而不是include头文件。import解决了重复编译的问题。当然,Go也使用了import方法;在模板编译方面,由于Go在设计理念上遵循简单性,所以函数式编程不包含在设计框架中,因此不存在模板编译带来的时间开销(缺乏泛型支持也是原因)很多人对Go语言不满意)。在Go1.5版本中,Go团队使用Go语言编写了Go语言编译器(也称为bootstrapping)。与gccgo相比:编译速度提高,但执行速度略有下降(性能细节不如优化gcc)增加了可编译平台类型(以前受gcc限制)。另外,Go语言语法中的关键字非常少(Go1.11版本只有25个)[7],这也可以减少编译器花在语法解析上的时间开销。所以在我看来,Go编译速度更快的主要原因有四个:导入引用管理;没有模板编译负担;1.5版本后的bootstrap编译器优化;更少的关键字。因此,为了加快编译速度而放弃C++转而使用Go,我们还要考虑是否放弃泛型编程的优势。注意:Go2版本可能支持泛型。关于Go的实际性能和Go的执行速度,可以参考一个语言性能测试数据网站——TheComputerLanguageBenchmarksGame[8]。这个网站在不同的算法上测试了每种语言,然后给出了时间和内存开销数据的比较。比较的语言有C++、Java、Python。首先是时间成本:注:时间成本的单位是s,Y轴是为了方便在不同跨度上进行比较,所以选择对数轴(即非线性轴,即1-10-100-1000的比较跨度)。然后是内存开销:注:为了方便不同跨度的比较,这里选择Y轴为对数轴(即非线性轴,是1000-10000-100000-1000000的比较跨度).需要注意的是,语言本身的性能只决定了程序的最高理论性能,程序的具体性能取决于程序的实现方式。因此,当每种语言的性能相差不大时,性能往往只取决于程序是如何实现的。两张图中的数据可以分析:Go虽然不能达到C++的极致性能,但在大多数情况下已经很接近了;Go和Java在算法的时间成本上几乎相当,但在内存成本方面Java高得多;Go在上述大部分情况下,至少时间和内存开销都比Python好很多;Go的并发编程Go的并发是比较流行的,网上很多内容都集中在几个方面:天然的并发设计轻量级并发编程更高的并发性能轻量级线程Goroutines,并发通信Channels等方便的并发同步控制工具由于Go在设计时有考虑到并发支持,也就是说很多特性都是为并发而设计的,这与后来的一些库和第三方库中支持并发的语言是不一样的。那么Go的并发有多方便呢?在Go中使用并发,只需要在普通函数执行前加上一个go关键字,就可以创建一个新的线程让函数在里面执行:funcmain(){goloop()//启动一个线程的好处goroutineloop()}不仅仅是为了让并发编程更方便,在一些特定的情况下,比如Go在引用一些使用并发的库时,这些库使用的并发也是基于Go本身的并发设计的,会有不会出现库使用另一套并发实现的情况,这样Go调度器在处理程序中的各种并发线程时可以有更统一的管理方式。但是Go的并发性对程序实现的要求比较高。在使用某些通信通道时,稍有疏忽就可能出现死锁问题,如:fatalerror:allgoroutinesareasleep-deadlock!Go的并发性可以和大多数语言相媲美。环境中普通线程的实现更高,这得益于轻量级的Goroutine。重量轻的主要原因是它占用的空间要小得多。例如64位环境下的JVM默认会为每个线程分配1MB。Thread栈空间,而Goroutines只有4-8KB左右,然后按需分配。足够轻量级的线程可以在相同内存下(在服务器CPU不饱和的情况下)有更高的并发,也可以减少很多上下文切换时间开销[9]。但是如果你的每个线程占用的空间都非常大(比如10MB,当然非常规的需求都是这样),Go的轻量级优势就没有那么明显了。Go在并发方面的优势是显而易见的,也是Go的功能目标。它在语言设计上支持并发,并提供了统一便捷的工具。复杂的并发业务也需要在Go的一套完整的并发规范体系下进行编程。当然,这会牺牲一定的自由度,但可以降低性能提升和维护成本。PS:关于Go调度器的内容这里就不多说了,因为很难用简单的语言向读者解释这种调度方式和其他调度方式的优缺点。Go将在以后的文章中详细介绍。调度程序的内容。Go中的垃圾收集垃圾收集(英文:GarbageCollection,简称GC)是计算机科学中的一种自动内存管理机制。当不再需要计算机上的动态内存时,应释放它以为内存腾出空间。这种内存资源管理称为垃圾回收。垃圾收集器可以减轻程序员的许多负担,减少程序员犯错的机会[10]。在使用Go或其他支持GC的语言时,不需要像C++那样手动释放(free/delete)不需要的变量占用的内容空间。确实,这很方便(对于懒惰的人和那些容易忘记主动释放的人),但它也有一些限制(黑盒操作的不透明性和GC处理的性能开销)。GC不是万灵药。当遇到一些对性能要求很高的场景时,还是要记得进行一些主动释放或者优化操作(比如自定义内存池)。PS:我会在以后的文章中详细介绍Go垃圾回收的细节(如果你也觉得有必要的话)。什么时候可以选择Go?Go有很多优点,比如编译速度快、性能好、自然并发、垃圾回收等。还有很多比较有特色的内容还没有提到(比如gofmt)。Go语言也有很多缺点,比如第三方库支持不够(相比Python少了很多),支持编译的平台不够广,叫nightmare的依赖版本管理(已经在改进,但还不完全可靠)。那么Go适合做什么,不适合做什么呢?分析了这么多,这个问题其实很难回答,但是我们可以选择先把不合适的地方剔除掉,看看会剩下什么。Go不适合极端高性能的优化场景,你可能需要使用C/C++,甚至汇编;简单的流程脚本工具、数值分析、深度学习,也许Python更适合(至少目前是这样);建立博客或网站,PHP不是世界第一语言;如果想更方便的找一份后端的工作,Java职位在大多数公司一直很缺人(在实际生产过程中,目前Go还是比不上Java)要好很多,至少没那么好一个部门/公司将其核心业务重构为Go进行重构);...你可以找到很多像上面这样的场景,你可能会发现围棋作为替代品losewho并不是那么完美。Go适合做什么最后,我们来到我们的终极问题,Go适合做什么?读到这里,你可能会觉得我把Go的特性吹了,然后突然告诉你,Go可能不适合你。Go天生就是并发的,并且是面向并发的,所以Go的定位一直很明确。从最简单的角度来看,至少Go作为高性能并发后端是非常有吸引力的。尤其是后端程序员,在一些业务功能的初始实现中,简洁的语法、内置的并发、快速的编译都可以让你更高效、更快速地完成任务(前提是Go的内容足够)完成你的任务),不用担心编译优化和内存回收,不用担心时间和内存开销过大,不用担心不同版本库(静态编译)之间的冲突,不用担心交叉编译平台适配问题。大多数情况下,编写一个服务,只需要:实现、编译、部署、运行。够高效、够快、够敏捷,这适用于企业大部分项目的起步阶段,这也是大部分项目对发展起步阶段的要求。当一个项目或服务真正能够发展起来,需求确实触及Go的天花板时,再考虑使用更好的语言或方法进行优化也不晚。总之,虽然Go的简洁带来了很多问题(有人说太简单了),但Go的优点是大多数人都可以使用编程语言工具来解决他们难以解决的问题。更重要的问题。Go语言不是灵丹妙药,但它确实有效地解决了这些问题。