Go并发编程实战
上QQ阅读APP看书,第一时间看更新

1.1 Go的语言特性

Go语言于2009年11月正式发布,它是谷歌公司开发的一种语法简单、原生支持并发、静态强类型的编译型语言。Go程序可以运行在多种平台上,可在Linux、Mac OS X和Microsoft Windows操作系统上运行。在撰写此书时,Go最新的版本为1.13.4。Go语言在设计时吸收了众多语言的优势,并尽量保持简洁且实用。

Go语言在语法上和C语言非常相似,被称为面向21世纪的C语言,可见其在编程语言中的地位。Go语言的设计理念是大道至简(Less Can Be More)。

Go语言的三位创始人是编程界的大神级人物,分别是罗伯特·格瑞史莫(Robert Griesemer)、罗勃·派克(Rob Pike)及肯·汤普逊(Ken Thompson)。其中,Ken Thompson是UNIX的发明人之一、C语言的前身B语言的发明者和1983年的图灵奖获得者,而Robert Griesemer是Google V8 JS Engine和Hot Spot的开发者。

Go语言虽然是静态编译型语言,但是它拥有脚本化的语法,支持多种编程范式,如支持函数式编程和面向对象编程。原生支持并发编程的特性应该是Go语言最让人着迷的地方,开发人员可以通过goroutine这种轻量级线程的技术来实现并发编程的目标。

Go语言主要在多核硬件架构、超大规模分布式计算集群和Web应用上具有明显的优势。Go语言最主要的特性有如下几个方面。

1.原生支持并发

对于Web应用来说,高并发和高性能是其追求的目标之一。就高并发的程序而言,如何保证线程安全非常重要。与Java和.NET等语言相比,Go语言在并发编程方面要简洁很多,这也是Go语言适合编写高并发、高性能应用的前提。Go语言的并发执行单元被称为协程(Goroutine),可以看作是一种微线程,它比线程更轻量、开销更小、性能更高。Go语言原生提供关键字go来启动协程,在一台机器上可以启动成千上万个协程。

注意

Go语言原生支持并发,但是不能保证并发时线程一定是安全的,线程安全仍然需要用锁等技术进行实现。

2.自动垃圾回收

C和C++编写的程序性能往往很高、运行速度比较快,但是这两种语言需要开发人员手动管理内存,包括内存的申请和释放等。由于没有垃圾回收机制,因此C和C++编写的程序一不小心就可能会导致内存泄漏,进而导致程序甚至系统崩溃。内存泄漏这种问题不易发现并且难以调试。内存泄漏的最佳解决方案是在语言级别引入自动垃圾回收机制(Garbage Collection,GC)。

Java和.NET等高级语言引入了垃圾回收机制,内存释放由虚拟机(Virtual Machine)或运行时(Runtime)系统来自动进行管理。Go语言也具有自动垃圾回收机制,因此可以解放开发人员让他们专注业务逻辑的实现。

注意

Go语言虽然有自动垃圾回收机制,但是在编写代码时,对于确定不用的对象,建议手动进行释放,这样可以避免自动垃圾回收机制不及时对内存进行回收而导致的内存泄漏问题。

3.更丰富的内置数据类型

Go语言除了内置简单数据类型(如整数类型和浮点类型等)和高级数据类型(数组类型和字符串类型)外,还内置了map类型(字典类型,也被称为映射类型)和slice类型(切片类型)。slice类型底层其实是一种可动态增长的数组。这几种数据类型基本上覆盖了绝大部分的应用场景。数组切片的功能与C++标准库中的vector非常类似。

由于Go语言内置了更加丰富的数据类型,因此编写代码就更加简洁,例如不用额外导入包或模块就可以使用map和slice类型。

4.函数可有多个返回值

当前的主流语言中除Python外,基本都不支持函数有多个返回值,比如C和C++。虽然这项功能非常实用,但是C语言并不支持,C语言为了解决多个返回值的问题,一般只能将函数返回值定义为一个结构体。

而Go语言原生就支持函数有多个返回值。在Go语言中,如果开发者只需要用到函数某几个返回值,而不是全部的返回值,则可以用下划线(_)作为占位符来忽略不关心的返回值。

5.语言的互操作性和指针

Go语言的互操作性指的是可以和其他语言进行互操作,如调用其他语言编译的库。Go语言允许开发者调用C语言代码。

此外,C语言和C++中很重要的一个概念就是指针。指针比较抽象,而且不容易掌握。Go语言也提供了指针,但并不能像C语言那样进行指针运算(安全模式下),而必须在所谓的非安全模式下。Go语言允许我们控制特定集合的数据结构、分配的数量以及内存访问模式,这对于构建运行良好的系统是非常重要的。正确地使用指针,可以达到提升性能和减少内存占用的目的。在系统编程或者网络应用等方面,指针往往不可或缺。

6.异常处理

Go语言不支持try...catch这种结构化的异常处理方式,因为这种异常处理会增加代码量且可能会被滥用。Go语言提倡的异常处理方式是:

· 普通异常:被调用方返回error对象,调用方判断error对象。

· 严重异常:中断性panic(比如除0),使用defer...recover...panic机制来捕获处理。严重异常一般由Go内部自动抛出,不需要用户主动抛出。当然,开发人员也可以使用panic主动抛出异常(错误)。

7.类型推导和接口

Go语言支持“var a =7”语法,这让Go语言有点像动态类型语言,但它实际上是强类型语言,只是变量a定义时会被自动推导出是整数类型。Go语言在代码风格上像动态类型语言,在运行效率上则像静态编译型语言。

Go语言用关键字struct来自定义类型,与C语言中的结构非常接近。同时Go语言还引入了一个无比强大的非侵入式接口的概念,只要某个对象实现了某个接口的定义,就会认为实现了该接口,而不用显式的语法来实现。

Go语言提供了灵活、无继承的类型系统,无须降低运行性能就能最大程度复用代码。这个类型系统依然支持面向对象的开发,同时避免了传统面向对象的问题。在Go语言中,一个类型由其他更微小的类型组合而成,从而避免了使用传统的基于继承的类型系统。

8.规范的语法

Go语言的编程规范强制融入到语言中,比如明确规定大括号摆放的位置、强制要求一行一条程序语句、不允许导入没有使用的包、不允许定义没有使用的变量、提供gofmt工具强制格式化代码等。

从工程管理的角度,任何一个开发团队都会对特定的语言制定一定的编程规范,方便团队协作。Go语言的设计者们认为,将规范写在文档里,还不如从语言层面以强制方式进行约束,这样更加直接,更有利于团队协作和工程管理。

Go语言规范的语法,可以让不同团队编写的代码更加趋于一致,这样也更加易于理解。Go语言编写的程序往往比其他语言更加易于阅读、更加简洁。

9.闭包和匿名函数

在Go语言中,函数可以作为另外一个函数的参数进行传递。Go语言支持常规的匿名函数和闭包,开发者可以随意对该匿名函数变量进行传递和调用。Go语言闭包经常出现在这样的应用场景中,一个函数的返回值作为参数传递给另一个函数。

在Go语言中,闭包的价值在于函数可以存储到变量中,作为参数传递给其他函数,最重要的是能够被函数动态创建和返回。Go语言中的闭包同样也会引用到函数外的变量。闭包的实现确保只要闭包还被使用,被闭包引用的变量就会一直存在。

注意

由于闭包会使得函数中的变量都被保存在内存中,只要闭包在使用,其引用的变量就不会消失,因此内存消耗比较大。

10.反射

反射(reflect)技术是一种比较高级的功能,经常出现在框架类的工具开发中,在Java或者.NET语言中会用到。反射就是用来检测存储在接口变量内部值(Value)和类型(Type)的一种机制。Go语言的反射包提供了两个方法,分别是reflect.ValueOf和reflect.TypeOf。

通过反射,我们可以获取对象类型的详细信息,并可动态操作对象。反射虽然功能非常强大,但是代码可读性不高且执行效率比较低。因此若没有必要,不推荐使用反射。

11.内置Runtime

Go语言和Java不同,没有JVM虚拟机这一层,Go程序编译后的二进制文件中内置了Go Runtime相关库。因此,发布Go程序无须安装额外的运行时环境。Runtime负责管理任务调度、垃圾收集与运行环境等。