Go语言简史
对语言进行评估时,明白设计者的动机以及语言要解决的问题很重要。Go 语言出自 Ken Thompson 和 Rob Pike、Robert Griesemer 之手,他们都是计算机科学领域的重量级人物。
- 在 20 世纪 70 年代,Ken Thompson 设计并实现了最初的 UNIX 操作系统,仅从这一点说,他对计算机科学的贡献怎么强调都不过分。他还与 Rob Pike 合作设计了 UTF-8 编码方案。
- 除帮助设计 UTF-8 外,Rob Pike 还帮助开发了分布式多用户操作系统 Plan 9,并与人合著了《The Unix Programming Environment》,对 UNIX 的设计理念做了正统的阐述。
- Robert Griesemer 就职于 Google,对语言设计有深入的认识,并负责 Chrome 浏览器和 Node.js 使用的 Google V8 JavaScript 引擎的代码生成部分。
这些计算机科学领城的重量级人物设计 Go 语言的初衷是满足 Google 的需求。设计此语言花费了两年的时间,融入了整个团队多年的经验及对编程语言设计的深入认识。设计团队借鉴了 Pascal、Oberon 和C语言的设计智慧,同时让 Go 语言具备动态语言的便利性。因此,Go 语言体现了经验丰富的计算机科学家的语言设计理念,是为全球最大的互联网公司之一设计的。
Go 语言的所有设计者都说,设计 Go 语言是因为 C++ 给他们带来了挫败感。在 Google I/O 2012 的 Go 设计小组见面会上,Rob Pike 是这样说的:
我们做了大量的 C++ 开发,厌烦了等待编译完成,尽管这是玩笑,但在很大程度上来说也是事实。
您无须知道 Go 语言的设计历史就能使用它。您只需知道,Go 语言的设计和实现体现了多位计算机专家多年的经验以及对其他编程语言优缺点的深入认识。因 C++ 的不良体验而出现的 Go 语言是一门现代编程语言,可用来创建性能卓越的 Web 服务器和系统程序。
Go 是编译型语言
Go 使用编译器来编译代码。编译器将源代码编译成二进制(或字节码)格式;在编译代码时,编译器检查错误、优化性能并输出可在不同平台上运行的二进制文件。要创建并运行 Go 程序,程序员必须执行如下步骤。
- 使用文本编辑器创建 Go 程序;
- 保存文件;
- 编译程序;
- 运行编译得到的可执行文件。
这不同于 Python、Ruby 和 JavaScript 等语言,它们不包含编译步骤。Go 自带了编译器,因此无须单独安装编译器。
为什么要学习 Go 语言
如果你要创建系统程序,或者基于网络的程序,Go 语言是很不错的选择。作为一种相对较新的语言,它是由经验丰富且受人尊敬的计算机科学家设计的,旨在应对创建大型并发网络程序面临的挑战。
如果你觉得 Java 或 C/C++ 的语法导致编程困难,那么 Go 语言将可能提供更佳的体验。
对于具备诸如 Ruby、Python、JavaScript 等动态语言使用经验的程序员来说,Go 语言提供了类型安全,同时又不像传统语言那么死板。
Go 语言吉祥物
Go 语言有一个吉祥物,在会议、文档页面和博文中,大多会包含下图所示的 Go Gopher,这是才华横溢的插画家 Renee French 设计的,她也是 Go 设计者之一 Rob Pike 的妻子。
Go语言为并发而生
现今,多核 CPU 已经成为服务器的标配,但是对多核的运算能力挖掘一直由程序员人工设计算法及框架来完成,这个过程需要开发人员具有一定的并发设计及框架设计能力。
虽然一些编程语言的框架在不断地提高多核资源使用效率,例如 Java 的 Netty 等,但仍然需要开发人员花费大量的时间和精力搞懂这些框架的运行原理后才能熟练掌握。
Go 语言在多核并发上拥有原生的设计优势。Go 语言从 2009 年 11 月开源,2012 年发布 Go 1.0 稳定版本以来,已经拥有活跃的社区和全球众多开发者,并且与苹果公司的 Swift 一样,成为当前非常流行的开发语言之一。
很多公司,特别是中国的互联网公司,即将或者已经完成了使用 Go 语言改造旧系统的过程。经过 Go 语言重构的系统能使用更少的硬件资源而有更高的并发和 I/O 吞吐表现。
Go语言从底层原生支持并发,无须第三方库、开发者的编程技巧和开发经验就可以轻松地在 Go 语言运行时来帮助开发者决定怎么使用 CPU 资源。
Go语言的并发是基于 goroutine 的,goroutine 类似于线程,但并非线程。可以将 goroutine 理解为一种虚拟线程。Go 语言运行时会参与调度 goroutine,并将 goroutine 合理地分配到每个 CPU 中,最大限度地使用CPU性能。
多个 goroutine 中,Go 语言使用通道(channel)进行通信,程序可以将需要并发的环节设计为生产者模式和消费者的模式,将数据放入通道。通道的另外一端的代码将这些数据进行并发计算并返回结果,如下图所示。
下面代码中的生产者每秒生成一个字符串,并通过通道传给消费者,生产者使用两个 goroutine 并发运行,消费者在 main() 函数的 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,和通道实现数据交换。