Go语言简介
Go语言简史
Go 语言(或 Golang)是 Google 在 2007 年开发的一种开源编程语言,于 2009 年 11 月 10 日向全球公布。Go 是非常年轻的一门语言,它的主要目标是“兼具 Python 等动态语言的开发速度和 C/C++等编译型语言的性能与安全性”。
Go 语言是编程语言设计的又一次尝试,是对类C语言的重大改进,它不但能让你访问底层操作系统,还提供了强大的网络编程和并发编程支持。Go 语言的用途众多,可以进行网络编程、系统编程、并发编程、分布式编程。
Go 语言的推出,旨在不损失应用程序性能的情况下降低代码的复杂性,具有“部署简单、并发性好、语言设计良好、执行性能好”等优势,目前国内诸多 IT 公司均已采用 Go 语言开发项目。
此外,很多重要的开源项目都是使用 Go 语言开发的,其中包括 Docker、Go-Ethereum、Thrraform 和 Kubernetes。
对语言进行评估时,明白设计者的动机以及语言要解决的问题很重要。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语言垃圾回收
1 内存自动回收,再也不需要开发人员管理内存
2 开发人员专注业务实现,降低了心智负担
3 只需要new分配内存,不需要释放
Go语言为并发而生
为什么要学习 Go 语言
如果你要创建系统程序,或者基于网络的程序,Go 语言是很不错的选择。作为一种相对较新的语言,它是由经验丰富且受人尊敬的计算机科学家设计的,旨在应对创建大型并发网络程序面临的挑战。
如果你觉得 Java 或 C/C++ 的语法导致编程困难,那么 Go 语言将可能提供更佳的体验。
对于具备诸如 Ruby、Python、JavaScript 等动态语言使用经验的程序员来说,Go 语言提供了类型安全,同时又不像传统语言那么死板。
Go 语言吉祥物
Go 语言有一个吉祥物,在会议、文档页面和博文中,大多会包含下图所示的 Go Gopher,这是才华横溢的插画家 Renee French 设计的,她也是 Go 设计者之一 Rob Pike 的妻子。
现今,多核 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 语言从发布 1.0 版本以来备受众多开发者关注并得到广泛使用,Go 语言的简单、高效、并发特性吸引了众多传统语言开发者的加入,而且人数越来越多
哪些项目使用Go语言开发
使用 Go 语言开发的开源项目非常多。早期的 Go 语言开源项目只是通过 Go 语言与传统项目进行C语言库绑定实现,例如 Qt、Sqlite 等;后期的很多项目都使用 Go 语言进行重新原生实现,这个过程相对于其他语言要简单一些,这也促成了大量使用 Go 语言原生开发项目的出现
下面列举的是原生使用 Go 语言进行开发的部分项目。
Docker
Docker 是一种操作系统层面的虚拟化技术,可以在操作系统和应用程序之间进行隔离,也可以称之为容器。Docker 可以在一台物理服务器上快速运行一个或多个实例。例如,启动一个 CentOS 操作系统,并在其内部命令行执行指令后结束,整个过程就像自己在操作系统一样高效。
项目链接: https://github.com/docker/docker
go语言
Go 语言自己的早期源码使用C语言和汇编语言写成。从 Go 1.5 版本后,完全使用 Go 语言自身进行编写。Go 语言的源码对了解 Go 语言的底层调度有极大的参考意义,建议希望对 Go 语言有深入了解的读者读一读。
项目链接: https://github.com/golang/go
Kubernetes
Google 公司开发的构建于 Docker 之上的容器调度服务,用户可以通过 Kubernetes 集群进行云端容器集群管理
项目链接: https://github.com/kubernetes/kubernetes
etcd
一款分布式、可靠的 KV 存储系统,可以快速进行云配置。
项目链接: https://github.com/coreos/etcd
beego
beego 是一个类似 Python 的 Tornado 框架,采用了 RESTFul 的设计思路,使用 Go 语言编写的一个极轻量级、高可伸缩性和高性能的 Web 应用框架。
项目链接: https://github.com/astaxie/beego
martini
一款快速构建模块化的 Web 应用的 Web 框架。
项目链接: https://github.com/go-martini/martini
codis
国产的优秀分布式 Redis 解决方案。
项目链接: https://github.com/CodisLabs/codis
delve
Go语言强大的调试器,被很多集成环境和编辑器整合。
项目链接: https://github.com/derekparker/delve
Go语言标准库
Go语言的标准库覆盖网络、系统、加密、编码、图形等各个方面,可以直接使用标准库的 http 包进行 HTTP 协议的收发处理;网络库基于高性能的操作系统通信模型(Linux 的 epoll、Windows 的 IOCP);所有的加密、编码都内建支持,不需要再从第三方开发者处获取。
Go 语言的编译器也是标准库的一部分,通过词法器扫描源码,使用语法树获得源码逻辑分支等。Go 语言的周边工具也是建立在这些标准库上。在标准库上可以完成几乎大部分的需求。
Go 语言的标准库以包的方式提供支持,下表是 Go 语言标准库中常见的包及其功能。
Go语言标准库常用的包及功能
Go语言标准库包名 | 功 能 |
bufio | 带缓冲的 I/O 操作 |
bytes | 实现字节操作 |
封装堆、列表和环形列表等容器 | |
crypto | 加密算法 |
database | 数据库驱动和接口 |
debug | 各种调试文件格式访问及调试功能 |
encoding | 常见算法如 JSON、XML、Base64 等 |
flag | 命令行解析 |
fmt | 格式化操作 |
go | Go 语言的词法、语法树、类型等。可通过这个包进行代码信息提取和修改 |
html | HTML 转义及模板系统 |
image | 常见图形格式的访问及生成 |
io | 实现 I/O 原始访问接口及访问封装 |
math | 数学库 |
net | 网络库,支持 Socket、HTTP、邮件、RPC、SMTP 等 |
os | 操作系统平台不依赖平台操作封装 |
path | 兼容各操作系统的路径操作实用函数 |
plugin | Go 1.7 加入的插件系统。支持将代码编译为插件,按需加载 |
reflect | 语言反射支持。可以动态获得代码中的类型信息,获取和修改变量的值 |
regexp | 正则表达式封装 |
runtime | 运行时接口 |
sort | 排序接口 |
strings | 字符串转换、解析及实用函数 |
time | 时间接口 |
text | 文本模板及 Token 词法器 |
Go语言上手简单
Go 语言简单易学,学习曲线平缓,不需要像 C/C++ 语言动辄需要两到三年的学习期。Go 语言被称为“互联网时代的C语言”。互联网的短、频、快特性在 Go 语言中体现得淋漓尽致。一个熟练的开发者只需要短短的一周时间就可以从学习阶段转到开发阶段,并完成一个高并发的服务器开发。
Go 语言是 Google 公司开发的一种静态型、编译型并自带垃圾回收和并发的编程语言。
Go 语言的风格类似于C语言。其语法在C语言的基础上进行了大幅的简化,去掉了不需要的表达式括号,循环也只有 for 一种表示方法,就可以实现数值、键值等各种遍历。因此,Go 语言上手非常容易。
很多读者表示自己是在看了介绍后才开始了解这门语言的,他们一般也会使用两到三门编程语言。Go 语言对于他们来说,也就是一到两天的熟悉过程,之后就可以开始使用 Go 语言解决具体问题了,大约一周左右已经可以使用 Go 语言完成既定的任务了。
Go 语言这种从零开始使用到解决问题的速度,在其他语言中是完全不可想象的。学过 C++ 的朋友都知道,一到两年大强度的理论学习和实战操练也只能学到这门语言的皮毛,以及知道一些基本的避免错误的方法。
那么,Go 语言到底有多么简单?下面从实现一个 HTTP 服务器开始了解。
HTTP 文件服务器是常见的 Web 服务之一。开发阶段为了测试,需要自行安装 Apache 或 Nginx 服务器,下载安装配置需要大量的时间。使用 Go 语言实现一个简单的 HTTP 服务器只需要几行代码,如下所示
package main
import (
"net/http"
)
func main() {
http.Handle("/", http.FileServer(http.Dir(".")))
http.ListenAndServe(":8080", nil)
}
下面是代码说明:
- 第 1 行,标记当前文件为 main 包,main 包也是 Go 程序的入口包。
- 第 3~5 行,导入 net/http 包,这个包的作用是 HTTP 的基础封装和访问。
- 第 7 行,程序执行的入口函数 main()。
- 第 8 行,使用 http.FileServer 文件服务器将当前目录作为根目录(
/
目录)的处理器,访问根目录,就会进入当前目录。 - 第 9 行,默认的 HTTP 服务侦听在本机 8080 端口。
把这个源码保存为 main.go(Go 语言的源文件后缀就是.go
),安装 Go 语言的开发包(后续我们会讲解如何安装),在命令行输入如下命令:
$ go run main.go
在浏览器里输入http://127.0.0.1:8080
即可浏览文件,这些文件正是当前目录在HTTP服务器上的映射目录。
Go语言代码风格清晰、简单
Go 语言工程结构简单
Go 语言的源码无须头文件,编译的文件都来自于后缀名为.go
的源码文件。
Go 语言无须解决方案、工程文件和 Make File,只要将工程文件按照 GOPATH 的规则进行填充,即可使用 go build/go install 进行编译,编译安装的二进制可执行文件统一放在 bin 文件夹下。
后面的章节会介绍 GOPATH 及 go build/go install 的详细使用方法。
Go 语言编译速度快
Go 语言可以利用自己的特性实现并发编译,并发编译的最小元素是包。从 Go 1.9 版本开始,最小并发编译元素缩小到函数,整体编译速度提高了 20%。
另外,Go 语言语法简单,具有严谨的工程结构设计、没有头文件、不允许包的交叉依赖等规则,在很大程度上加速了编译的过程。
Go 语言写起来类似于C语言,因此熟悉C语言及其派生语言(C++、C#、Objective-C 等)的人都会迅速熟悉这门语言。
C语言的有些语法会让代码可读性降低甚至发生歧义。Go 语言在C语言的基础上取其精华,弃其糟粕,将C语言中较为容易发生错误的写法进行调整,做出相应的编译提示。
去掉循环冗余括号
Go 语言在众多大师的丰富实战经验的基础上诞生,去除了C语言语法中一些冗余、烦琐的部分。下面的代码是C语言的数值循环
// C语言的for数值循环
for(int a = 0;a<10;a++){
// 循环代码
}
在Go语言中,这样的循环变为:
for a := 0;a<10;a++{
// 循环代码
}
for 两边的括号被去掉,int 声明被简化为:=
,直接通过编译器右值推导获得 a 的变量类型并声明。
去掉表达式冗余括号
同样的简化也可以在判断语句中体现出来,以下是C语言的判断语句
if (表达式){
// 表达式成立
}
在 Go 语言中,无须添加表达式括号,代码如下
if 表达式{
// 表达式成立
}
强制的代码风格
Go 语言中,左括号必须紧接着语句不换行。其他样式的括号将被视为代码编译错误。这个特性刚开始会使开发者有一些不习惯,但随着对 Go 语言的不断熟悉,开发者就会发现风格统一让大家在阅读代码时把注意力集中到了解决问题上,而不是代码风格上。
同时 Go 语言也提供了一套格式化工具。一些 Go 语言的开发环境或者编辑器在保存时,都会使用格式化工具进行修改代码的格式化,让代码提交时已经是统一格式的代码。
不再纠结于 i++ 和 ++i
C语言非常经典的考试题为
int a, b;
a = i++;
b = ++i;
这种题目对于初学者简直摸不着头脑。为什么一个简单的自增表达式需要有两种写法?
在 Go 语言中,自增操作符不再是一个操作符,而是一个语句。因此,在 Go 语言中自增只有一种写法
i++
如果写成前置自增++i
,或者赋值后自增a=i++
都将导致编译错误。
GOPATH 及相关的目录命名是 Go 语言编译的核心规则。
Go 开发包的安装目录的功能及说明
目录名 | 说明 |
api | 每个版本的 api 变更差异 |
bin | go 源码包编译出的编译器(go)、文档工具(godoc)、格式化工具(gofmt) |
blog | Go 博客的模板,使用 Go 的网页模板,有一定的学习意义 |
doc | 英文版的 Go 文档 |
lib | 引用的一些库文件 |
misc | 杂项用途的文件,例如 Android 平台的编译、git 的提交钩子等 |
pkg | Windows 平台编译好的中间文件 |
src | 标准库的源码 |
test | 测试用例 |
开发时,无须关注这些目录。当读者希望深度了解底层原理时,可以通过上面的介绍继续探索