一.简介
- Go语言(或 Golang)起源于 2007 年,并在 2009 年正式对外发布。Go 是非常年轻的一门语言,它的主要目标是“兼具 Python 等动态语言的开发速度和 C/C++ 等编译型语言的性能与安全性”
- Go语言有时候被描述为“C 类似语言”,或者是“21 世纪的C语言”。Go 从C语言继承了相似的表达式语法、控制流结构、基础数据类型、调用参数传值、指针等很多思想,还有C语言一直所看中的编译后机器码的运行效率以及和现有操作系统的无缝适配。
- 因为Go语言没有类和继承的概念,所以它和 Java 或 C++ 看起来并不相同。但是它通过接口(interface)的概念来实现多态性。Go语言有一个清晰易懂的轻量级类型系统,在类型之间也没有层级之说。因此可以说Go语言是一门混合型的语言。
- Go 是编译型语言
- Go 使用编译器来编译代码。编译器将源代码编译成二进制(或字节码)格式;在编译代码时,编译器检查错误、优化性能并输出可在不同平台上运行的二进制文件。要创建并运行 Go 程序,程序员必须执行如下步骤。
- 使用文本编辑器创建 Go 程序;
- 保存文件;
- 编译程序;
- 运行编译得到的可执行文件。
这不同于 Python、Ruby 和 JavaScript 等语言,它们不包含编译步骤。Go 自带了编译器,因此无须单独安装编译器。
二.特性
语法简单
Go语言的语法处于简单和复杂的两极。C语言简单到你每写下一行代码,都能在脑中想象出编译后的模样,指令如何执行,内存如何分配,等等。而 C 的复杂在于,它有太多隐晦而不着边际的规则,着实让人头疼。相比较而言,Go 从零开始,没有历史包袱,在汲取众多经验教训后,可从头规划一个规则严谨、条理简单的世界。
并发模型
从根本上将一切都并发化,运行时用 Goroutine 运行所有的一切,包括 main.main 入口函数。
Goroutine 是 Go 最显著的特征。它用类协程的方式来处理并发单元,却又在运行时层面做了更深度的优化处理。这使得语法上的并发编程变得极为容易,无须处理回调,无须关注线程切换,仅一个关键字,简单而自然。
内存分配
Go 选择了 tcmalloc,它本就是为并发而设计的高性能内存分配组件
它会竭力将对象分配在栈上,以降低垃圾回收压力,减少管理消耗,提升执行性能。可以说,除偶尔因性能问题而被迫采用对象池和自主内存管理外,我们基本无须参与内存管理操作
垃圾回收
垃圾回收器必然是核心组件里修改最多的部分。从并发清理,到降低 STW 时间,直到 Go 的 1.5 版本实现并发标记,逐步引入三色标记和写屏障等等,都是为了能让垃圾回收在不影响用户逻辑的情况下更好地工作。尽管有了努力,当前版本的垃圾回收算法也只能说堪用,离好用尚有不少距离。
静态链接
将运行时、依赖库直接打包到可执行文件内部,简化了部署和发布操作,无须事先安装运行环境和下载诸多第三方库。这种简单方式对于编写系统软件有着极大好处,因为库依赖一直都是个麻烦。
标准库
Go 标准库虽称不得完全覆盖,但也算极为丰富。其中值得称道的是 net/http,仅须简单几条语句就能实现一个高性能 Web Server
工具链
Go 在此做得相当不错,无论是编译、格式化、错误检查、帮助文档,还是第三方包下载、更新都有对应的工具
内置完整测试框架,其中包括单元测试、性能测试、代码覆盖率、数据竞争,以及用来调优的 pprof,这些都是保障代码能正确而稳定运行的必备利器。
三.并发
Go语言从底层原生支持并发,无须第三方库,开发人员可以很轻松地在编写程序时决定怎么使用 CPU 资源。
Go语言的并发是基于 goroutine 的,goroutine 类似于线程,但并非线程。可以将 goroutine 理解为一种虚拟线程。Go语言运行时会参与调度 goroutine,并将 goroutine 合理地分配到每个 CPU 中,最大限度地使用 CPU 性能。
多个 goroutine 中,Go语言使用通道(channel)进行通信,通道是一种内置的数据结构,可以让用户在不同的 goroutine 之间同步发送具有类型的消息。这让编程模型更倾向于在 goroutine 之间发送消息,而不是让多个 goroutine 争夺同一个数据的使用权。
程序可以将需要并发的环节设计为生产者模式和消费者的模式,将数据放入通道。通道另外一端的代码将这些数据进行并发计算并返回结果,如下图所示。
package main
import (
"fmt"
"math/rand"
"time"
)
// 数据生产者
func producer(header string, channel chan<- string) {
// 无限循环, 不停地生产数据
for {
// 将随机数和字符串格式化为字符串发送给通道
channel <- fmt.Sprintf("%s: %v", header, rand.Int31())
// 等待1秒
time.Sleep(time.Second)
}
}
// 数据消费者
func customer(channel <-chan string) {
// 不停地获取数据
for {
// 从通道中取出数据, 此处会阻塞直到信道中返回数据
message := <-channel
// 打印数据
fmt.Println(message)
}
}
func main() {
// 创建一个字符串类型的通道
channel := make(chan string)
// 创建producer()函数的并发goroutine
go producer("cat", channel)
go producer("dog", channel)
// 数据消费函数
customer(channel)
}
运行结果:
dog: 2019727887
cat: 1298498081
dog: 939984059
cat: 1427131847
cat: 911902081
dog: 1474941318
dog: 140954425
cat: 336122540
cat: 208240456
dog: 646203300
对代码的分析:
- 第 03 行,导入格式化(fmt)、随机数(math/rand)、时间(time)包参与编译。
- 第 10 行,生产数据的函数,传入一个标记类型的字符串及一个只能写入的通道。
- 第 13 行,for{} 构成一个无限循环。
- 第 15 行,使用 rand.Int31() 生成一个随机数,使用 fmt.Sprintf() 函数将 header 和随机数格式化为字符串。
- 第 18 行,使用 time.Sleep() 函数暂停 1 秒再执行这个函数。如果在 goroutine 中执行时,暂停不会影响其他 goroutine 的执行。
- 第 23 行,消费数据的函数,传入一个只能写入的通道。
- 第 26 行,构造一个不断消费消息的循环。
- 第 28 行,从通道中取出数据。
- 第 31 行,将取出的数据进行打印。
- 第 35 行,程序的入口函数,总是在程序开始时执行。
- 第 37 行,实例化一个字符串类型的通道。
- 第 39 行和第 40 行,并发执行一个生产者函数,两行分别创建了这个函数搭配不同参数的两个 goroutine。
- 第 42 行,执行消费者函数通过通道进行数据消费。
整段代码中,没有线程创建,没有线程池也没有加锁,仅仅通过关键字 go 实现 goroutine,和通道实现数据交换。
四.Go语言适合做什么以及优势劣势
鉴于 Go 语言的特点和设计的初衷,从以下几个方面来分析 Go 语言擅长的领域:
(1).在服务器编程方面,Go 语言适合处理日志、数据打包、虚拟机处理、文件系统、分布式系统、数据库代理等;
(2). 网络编程方面,Go 语言广泛应用于 Web 应用、API 应用、下载应用等;
(3). 此外,Go 语言还可用于内存数据库和云平台领域,目前国外很多云平台都是采用 Go 开发
- 云计算基础设施领域,代表项目:docker、kubernetes、etcd、consul、cloudflare CDN、七牛云存储等。
- 基础软件,代表项目:tidb、influxdb、cockroachdb 等。
- 微服务,代表项目:go-kit、micro、monzo bank 的 typhon、bilibili 等。
- 互联网基础设施,代表项目:以太坊、hyperledger 等。
Go 语言的优势
Go 语言是集多编程范式之大成者,体现了优秀的软件工程思想和原则,其特性可以使开发者快速地开发、测试和部署程序,大大提高
了生产效率。下面我们来看看与其他主流语言相比,Go 语言具有的优势。
- 相对于 C/C++ 来讲,Go 语言拥有清晰的依赖管理和全自动的垃圾回收机制,因此其代码量大大降低,开发效率大大提高。
- 相对于 Java 来讲,Go 语言拥有简明的类型系统、函数式编程范式和先进的并发编程模型。因此其代码块更小更简洁、可重用性更
- 高,并可在多核计算环境下更快地运行。
- 对于 PHP 来讲,Go 语言更具通用性和规范性。这使得其更适合构建大型的软件,并能够更好地将各个模块组织在一起。在性能方
- 面,PHP 不可与 Go 同日而语。
- 对于 Python/Ruby 来讲,Go 的优势在于其简洁的语法、非侵入式和扁平化的类型系统和浑然天成的多范式编程模型。与 PHP 一样,
Python 和 Ruby 也是动态类型的解释型语言,这就意味着它们的运行速度会比静态类型的编译型语言慢很多。
总而言之,Go 语言对于当前大多数主流语言来讲,最大的优势在于具有较高的生产效率、先进的依赖管理和类型系统,以及原生的并
发计算支持。因此,Go 语言自发布以来就受到了各个领域开发者的关注和青睐。
Go 语言的劣势
下面,我们来客观地看一下目前 Go 语言需要加强或改进的地方(虽然有些 Gopher 并不这么认为)。
- 从分布式计算的角度来看,Go 语言的成熟度不及 Erlang(现在已经出现了一些这方面的 Go 语言代码包,我们已经可以看到光明的
- 未来了)。
- 从程序运行速度的角度来看,Go 语言虽然已与 Java 不相上下,但还不及 C(差距正在不断地缩小)。
- 从第三方库的角度来看,Go 语言的库数量还远远不及其他几门主流语言(比如 Java、Python、Ruby 等)。不过与 Go 语言的年纪
相比,用它实现的第三方库已经相当多了,并且它们的数量在持续地飞速增长中。
另外,在更深的层面,Go 语言标准库中也有些不尽如人意的的地方,具体如下。
- 从语言语法角度来看,Go 语言语法里的语法糖并不多,这让许多 Python、Ruby 爱好者们对它不屑一顾。另外,变量赋值方式多得
- 有点儿累赘了。最让人遗憾的也是我比较在意的一个地方是,Go 语言不支持自定义的泛型类型。
- 从并发编程角度来看,Go 语言提供的并发模型很强大,但也有一些编写规则需要了解。否则,很容易踩进“坑”里。其实不提倡把
- 这叫作“坑”。因为这些所谓的“坑”,大都是我们由于对原理不熟悉而自己挖出来的。
- 从垃圾回收角度看,Go 语言的垃圾回收采用的是并发的标记清除算法(Concurrent Mark and Sweep,CMS)。虽然是并发的操
作,时间比串行操作短很多,但是还是会在垃圾回收期间停止所有用户程序的操作。这一点多少会影响到对实时性要求比较高的应用。
不过,在 Go 语言 1.3 之后的版本中,这方面的问题已经得到了极大的改善。