- 第 31 行,将取出的数据进行打印。
- 第 35 行,程序的入口函数,总是在程序开始时执行。
- 第 37 行,实例化一个字符串类型的通道。
- 第 39 行和第 40 行,并发执行一个生产者函数,两行分别创建了这个函数搭配不同参数的两个 goroutine。
- 第 42 行,执行消费者函数通过通道进行数据消费。
整段代码中,没有线程创建,没有线程池也没有加锁,仅仅通过关键字 go 实现 goroutine,和通道实现数据交换。
哪些项目使用Go语言开发?
所有的编程语言都反映了语言设计者对编程哲学的反思,通常包括之前的语言所暴露的一些不足地方的改进。Go语言从发布 1.0 版本以来备受众多开发者关注并得到广泛使用,Go语言的简单、高效、并发特性吸引了众多传统语言开发者的加入,而且人数越来越多。
使用Go语言开发的开源项目非常多。早期的Go语言开源项目只是通过Go语言与传统项目进行C语言库绑定实现,例如 Qt、Sqlite 等;后期的很多项目都使用Go语言进行重新原生实现,这个过程相对于其他语言要简单一些,这也促成了大量使用Go语言原生开发项目的出现。
下面列举的是原生使用Go语言进行开发的部分项目。
1) Docker
Docker 是一种操作系统层面的虚拟化技术,可以在操作系统和应用程序之间进行隔离,也可以称之为容器。Docker 可以在一台物理服务器上快速运行一个或多个实例。例如,启动一个 CentOS 操作系统,并在其内部命令行执行指令后结束,整个过程就像自己在操作系统一样高效。
项目链接:https://github.com/docker/docker
2) Go语言
Go语言自己的早期源码使用C语言和汇编语言写成。从 Go 1.5 版本后,完全使用Go语言自身进行编写。Go语言的源码对了解Go语言的底层调度有极大的参考意义,建议希望对Go语言有深入了解的读者读一读。
项目链接:https://github.com/golang/go
3) Kubernetes
Google 公司开发的构建于 Docker 之上的容器调度服务,用户可以通过 Kubernetes 集群进行云端容器集群管理。系统会自动选取合适的工作节点来执行具体的容器集群调度处理工作。其核心概念是 Container Pod(容器仓)。
项目链接:https://github.com/kubernetes/kubernetes
4) etcd
一款分布式、可靠的 KV 存储系统,可以快速进行云配置。由 CoreOS 开发并维护键值存储系统,它使用Go语言编写,并通过 Raft 一致性算法处理日志复制以保证强一致性。
项目链接:https://github.com/coreos/etcd
5) beego
beego 是一个类似 Python 的 Tornado 框架,采用了 RESTFul 的设计思路,使用Go语言编写的一个极轻量级、高可伸缩性和高性能的 Web 应用框架。
项目链接:https://github.com/astaxie/beego
6) martini
一款快速构建模块化的 Web 应用的Go语言框架。
项目链接:https://github.com/go-martini/martini
7) codis
国产的优秀分布式 Redis 解决方案。可以将 codis 理解成为 Web 服务领域的 Nginx,它实现了对 Redis 的反向代理和负载均衡。
项目链接:https://github.com/CodisLabs/codis
8) delve
Go语言强大的调试器,被很多集成环境和编辑器整合。
项目链接:https://github.com/derekparker/delve
哪些大公司正在使用Go语言
Go语言是谷歌在 2009 年发布的一款编程语言,自面世以来它以高效的开发效率和完美的运行速度迅速风靡全球,被誉为“21 世纪的C语言”。
现在越来越多的公司开始使用Go语言开发自己的服务,同时也诞生了很多使用Go语言开发的服务和应用,比如 Docker、k8s 等,下面我们来看一下,有哪些大公司在使用Go语言。
1) Google
作为创造了Go语言的 google 公司,当然会力挺Go语言了。Google 有很多基于 Go 开发的开源项目,比如 kubernets,docker,大家可以参考《哪些项目使用Go语言开发》一节了解更多的Go语言开源项目。
2) Facebook
Facebook 也在使用Go语言,为此他们还专门在 Github 上建立了一个开源组织 facebookgo。大家可以通过 https://github.com/facebookgo 访问查看 facebook 开源的项目,其中最具代表性的就是著名平滑重启工具 grace。
3) 腾讯
腾讯在 15 年就已经做了 Docker 万台规模的实践。因为腾讯主要的开发语言是 C/C++ ,所以在使用Go语言方面会方便很多,也有很多优势,不过日积月累的 C/C++ 代码很难改造,也不敢动,所以主要在新业务上尝试使用 Go。
4) 百度
百度主要在运维方面使用到了Go语言,比如百度运维的一个 BFE 项目,主要负责前端流量的接入,其次就是百度消息通讯系统的服务器端也使用到了Go语言。
5) 七牛云
七牛云算是国内第一家选Go语言做服务端的公司。早在 2011 年,当Go语言的语法还没完全稳定下来的情况下,七牛云就已经选择将 Go 作为存储服务端的主体语言。
6) 京东
京东云消息推送系统、云存储,以及京东商城的列表页等都是使用Go语言开发的。
7) 小米
小米对Go语言的支持,在于运维监控系统的开源,它的官方网址是 http://open-falcon.org/。此外,小米互娱、小米商城、小米视频、小米生态链等团队都在使用Go语言。
8) 360
360 对Go语言的使用也不少,比如开源的日志搜索系统 Poseidon,大家可以通过 https://github.com/Qihoo360/poseidon 查看,还有 360 的推送团队也在使用Go语言。
除了上面提到的,还有很多公司开始尝试使用Go语言,比如美团、滴滴、新浪等。
Go语言的强项在于它适合用来开发网络并发方面的服务,比如消息推送、监控、容器等,所以在高并发的项目上大多数公司会优先选择 Golang 作为开发语言。
Go语言适合做什么
前面我们已经介绍过了Go语言的种种优势和不足,那么我们究竟可以使用Go语言来做些什么呢?
其实Go语言主要用作服务器端开发,其定位是用来开发“大型软件”的,适合于需要很多程序员一起开发,并且开发周期较长的大型软件和支持云计算的网络服务。
Go语言融合了传统编译型语言的高效性和脚本语言的易用性和富于表达性,不仅提高了项目的开发速度,而且后期维护起来也非常轻松。
鉴于Go语言的特点和设计的初衷,从以下几个方面来分析Go语言擅长的领域:
- 在服务器编程方面,Go语言适合处理日志、数据打包、虚拟机处理、文件系统、分布式系统、数据库代理等;
- 网络编程方面,Go语言广泛应用于 Web 应用、API 应用、下载应用等;
- 此外,Go语言还可用于内存数据库和云平台领域,目前国外很多云平台都是采用 Go 开发。
除了上面介绍到的,Go语言还可以用来开发底层,例如以太坊、超级账本等都是基于Go语言开发的。
而且对于现在比较流行的区块链技术方面,Go语言也是非常受欢迎的,很多基于区块链的 DApps(去中心化应用)和工具都是用的Go语言来实现的。
下面列举了一些基于Go语言开发的优秀开源项目:
- 云计算基础设施领域,代表项目:docker、kubernetes、etcd、consul、cloudflare CDN、七牛云存储等。
- 基础软件,代表项目:tidb、influxdb、cockroachdb 等。
- 微服务,代表项目:go-kit、micro、monzo bank 的 typhon、bilibili 等。
- 互联网基础设施,代表项目:以太坊、hyperledger 等。
总之,Go语言的优势还是比较多的,比如Go语言的性能非常出色,最关键的是在性能强劲的同时还能像解释型语言一样高效地进行开发。
Go语言和其它编程语言的对比
在软件行业做过一段时间的人都知道,没有万能的编程语言,也没有万能开发框架,更没有万能的解决方案。任何新技术的产生都应该归功于一部分人对老旧技术的强烈不满。Go语言也不例外。比如,C语言的依赖管理、C++ 的垃圾回收、Java 笨重的类型系统和厚重的 Java EE 规范,以及脚本语言(如 PHP、Python 和 Ruby)的性能,这些都是很多开发者社区经常争论和抱怨的问题。
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 之后的版本中,这方面的问题已经得到了极大的改善。
虽然Go语言还有一些瑕疵,但从整体来看,它已经是一门非常优秀的通用编程语言了。并且,Go语言在今后的发展上会关注性能、可靠性、可移植性和一些功能增强,所以上述缺憾会随着版本的推进而逐渐减弱和消失。
Go语言的性能如何?
根据 Go 开发团队和基本的算法测试,Go语言与C语言的性能差距大概在 10%~20% 之间。虽然没有官方的性能标准,但是与其它各个语言相比已经拥有非常出色的表现。
时下流行的语言大都是运行在虚拟机上,如:Java 和 Scala 使用的 JVM,C# 和 VB.NET 使用的 .NET CLR。尽管虚拟机的性能已经有了很大的提升,但任何使用 JIT 编译器和脚本语言解释器的编程语言(Ruby、Python、Perl 和 JavaScript)在 C 和 C++ 的绝对优势下甚至都无法在性能上望其项背。
这里以国外的一个编程语言性能测试网站 http://benchmarksgame.alioth.debian.org/ 为测试基准和数据源。这个网站可以对常见的编程语言进行性能比较,网站使用都是最新的语言版本和常见的一些算法。
通过对 C(gcc)、C++、Java、JavaScript 和Go语言的测试。性能比较如下表所示,表中数据的单位为秒,数值越小表明运行性能越好。
编程语言↓ / 测试用例→ | reverse-complement | pidigits | fannkuch-redux | fasta | spectral-norm | n-body | k-nucleotide | mandelbrot | binary-trees | regex-redux |
---|---|---|---|---|---|---|---|---|---|---|
C语言 | 0.42 | 1.73 | 8.97 | 1.33 | 1.99 | 9.96 | 5.38 | 1.65 | 2.38 | 1.45 |
C++ | 0.6 | 1.89 | 10.35 | 1.48 | 1.99 | 9.31 | 7.18 | 1.73 | 2.36 | 17.14 |
Go | 0.49 | 2.02 | 14.49 | 2.17 | 3.96 | 21.47 | 14.79 | 5.46 | 35.18 | 29.29 |
Java | 1.13 | 3.12 | 15.09 | 2.32 | 4.25 | 22.56 | 8.38 | 6.08 | 8.58 | 10.38 |
JavaScript | 4.3 | N/A | 81.49 | 9.79 | 16.17 | 28.74 | 66.07 | 19.04 | 53.64 | 4.44 |
通过上表可以看出,Go语言在性能上更接近于 Java 语言,虽然在某些测试用例上不如经过多年优化的 Java 语言,但毕竟 Java 语言已经经历了多年的积累和优化。Go语言在未来的版本中会通过不断的版本优化提高单核运行性能。
Go语言标准库强大
学习编程语言,早已不是学一点语法规则那么简单。现在更习惯称作选择 Ecosystem(生态圈),而这其中标准库的作用和分量尤为明显。
在Go语言的安装文件里包含了一些可以直接使用的包,即标准库。Go语言的标准库(通常被称为语言自带的电池),提供了清晰的构建模块和公共接口,包含 I/O 操作、文本处理、图像、密码学、网络和分布式应用程序等,并支持许多标准化的文件格式和编解码协议。
在 Windows 下,标准库的位置在Go语言根目录下的子目录 pkg\windows_amd64 中;在 Linux 下,标准库在Go语言根目录下的子目录 pkg\linux_amd64 中(如果是安装的是 32 位,则在 linux_386 目录中)。一般情况下,标准包会存放在
G
O
R
O
O
T
/
p
k
g
/
GOROOT/pkg/
GOROOT/pkg/GOOS_$GOARCH/ 目录下。
Go语言的编译器也是标准库的一部分,通过词法器扫描源码,使用语法树获得源码逻辑分支等。Go语言的周边工具也是建立在这些标准库上。在标准库上可以完成几乎大部分的需求。
Go语言的标准库以包的方式提供支持,下表列出了Go语言标准库中常见的包及其功能。
Go语言标准库包名 | 功 能 |
---|---|
bufio | 带缓冲的 I/O 操作 |
bytes | 实现字节操作 |
container | 封装堆、列表和环形列表等容器 |
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 提供了很好的参照。
Go语言上手简单
Go语言语法简单易懂,学习曲线平缓,不需要像 C/C++ 语言动辄需要两到三年的学习期。Go语言被称为“互联网时代的C语言”。互联网的短、频、快特性在Go语言中体现得淋漓尽致。一个熟练的开发者只需要短短的一周时间就可以从学习阶段转到开发阶段,并完成一个高并发的服务器开发。
Go语言是 Google 公司开发的一种静态型、编译型并自带垃圾回收和并发的编程语言。所以它是一门类型安全的语言,加上通过构建到本地代码,程序的执行速度也非常快。
Go语言的主要目标是将静态语言的安全性和高效性与动态语言的易开发性进行有机结合,达到完美平衡,从而使编程变得更加有乐趣,而不是在艰难抉择中痛苦前行。
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语言无须解决方案、工程文件和 Make File,只要将工程文件按照 GOPATH 的规则进行填充,即可使用 go build/go install 进行编译,编译完成的二进制可执行文件统一放在 bin 文件夹下。
后面的章节会介绍 GOPATH 及 go build/go install 的详细使用方法。
Go语言编译速度快
Go语言可以利用自己的特性实现并发编译,并发编译的最小元素是包。从 Go 1.9 版本开始,最小并发编译元素缩小到函数,整体编译速度提高了 20%。
另外,Go语言语法简单,具有严谨的工程结构设计、没有头文件、不允许包的交叉依赖等规则,在很大程度上加速了编译的过程。
Go语言代码风格清晰、简单
Go语言语法类似于C语言,因此熟悉C语言及其派生语言(C++、C#、Objective-C 等)的人都会迅速熟悉这门语言。
C语言的有些语法会让代码可读性降低甚至发生歧义。Go语言在C语言的基础上取其精华,弃其糟粕,将C语言中较为容易发生错误的写法进行调整,做出相应的编译提示。
1) 去掉循环冗余括号
Go语言在众多大师的丰富实战经验的基础上诞生,去除了C语言语法中一些冗余、烦琐的部分。下面的代码是C语言的数值循环:
// C语言的for数值循环
for(int a = 0;a<10;a++){
// 循环代码
}
在Go语言中,这样的循环变为:
for a := 0;a<10;a++{
// 循环代码
}
for 两边的括号被去掉,int 声明被简化为:=
,直接通过编译器右值推导获得 a 的变量类型并声明。
2) 去掉表达式冗余括号
同样的简化也可以在判断语句中体现出来,以下是C语言的判断语句:
if (表达式){
// 表达式成立
}
在Go语言中,无须添加表达式括号,代码如下:
if 表达式{
// 表达式成立
}
3) 强制的代码风格
Go语言中,左括号必须紧接着语句不换行。其他样式的括号将被视为代码编译错误。这个特性刚开始会使开发者有一些不习惯,但随着对Go语言的不断熟悉,开发者就会发现风格统一让大家在阅读代码时把注意力集中到了解决问题上,而不是代码风格上。
同时Go语言也提供了一套格式化工具。一些Go语言的开发环境或者编辑器在保存时,都会使用格式化工具对代码进行格式化,让代码提交时已经是统一格式的代码。
4) 不再纠结于 i++ 和 ++i
C语言非常经典的考试题为:
int a, b;
a = i++;
b = ++i;
这种题目对于初学者简直摸不着头脑。为什么一个简单的自增表达式需要有两种写法?
在Go语言中,自增操作符不再是一个操作符,而是一个语句。因此,在Go语言中自增只有一种写法:
i++
如果写成前置自增++i
,或者赋值后自增a=i++
都将导致编译错误。
Go语言是怎么完成编译的
Go语言是一门需要编译才能运行的编程语言,也就说代码在运行之前需要通过编译器生成二进制机器码,随后二进制文件才能在目标机器上运行,如果我们想要了解Go语言的实现原理,理解它的编译过程就是一个没有办法绕过的事情。
预备知识
想要深入了解Go语言的编译过程,需要提前了解一下编译过程中涉及的一些术语和专业知识。这些知识其实在我们的日常工作和学习中比较难用到,但是对于理解编译的过程和原理还是非常重要的。
1) 抽象语法树
在计算机科学中,抽象语法树(Abstract Syntax Tree,AST),或简称语法树(Syntax tree),是源代码语法结构的一种抽象表示。它以树状的形式表现编程语言的语法结构,树上的每个节点都表示源代码中的一种结构。
之所以说语法是“抽象”的,是因为这里的语法并不会表示出真实语法中出现的每个细节。比如,嵌套括号被隐含在树的结构中,并没有以节点的形式呈现。而类似于 if else 这样的条件判断语句,可以使用带有两个分支的节点来表示。
以算术表达式 1+3*(4-1)+2 为例,可以解析出的抽象语法树如下图所示:
图:抽象语法树
抽象语法树可以应用在很多领域,比如浏览器,智能编辑器,编译器。
2) 静态单赋值
在编译器设计中,静态单赋值形式(static single assignment form,通常简写为 SSA form 或是 SSA)是中介码(IR,intermediate representation)的属性,它要求每个变量只分配一次,并且变量需要在使用之前定义。在实践中我们通常会用添加下标的方式实现每个变量只能被赋值一次的特性,这里以下面的代码举一个简单的例子:
x := 1
x := 2
y := x
从上面的描述所知,第一行赋值行为是不需要的,因为 x 在第二行被二度赋值并在第三行被使用,在 SSA 下,将会变成下列的形式:
x1 := 1
x2 := 2
y1 := x2
从使用 SSA 的中间代码我们就可以非常清晰地看出变量 y1 的值和 x1 是完全没有任何关系的,所以在机器码生成时其实就可以省略第一步,这样就能减少需要执行的指令来优化这一段代码。
根据 Wikipedia(维基百科)对 SSA 的介绍来看,在中间代码中使用 SSA 的特性能够为整个程序实现以下的优化:
- 常数传播(constant propagation)
- 值域传播(value range propagation)
- 稀疏有条件的常数传播(sparse conditional constant propagation)
- 消除无用的程式码(dead code elimination)
- 全域数值编号(global value numbering)
- 消除部分的冗余(partial redundancy elimination)
- 强度折减(strength reduction)
- 寄存器分配(register allocation)
因为 SSA 的主要作用就是代码的优化,所以是编译器后端(主要负责目标代码的优化和生成)的一部分。当然,除了 SSA 之外代码编译领域还有非常多的中间代码优化方法,优化编译器生成的代码是一个非常古老并且复杂的领域,这里就不展开介绍了。
3) 指令集架构
最后要介绍的一个预备知识就是指令集架构了,指令集架构(Instruction Set Architecture,简称 ISA),又称指令集或指令集体系,是计算机体系结构中与程序设计有关的部分,包含了基本数据类型,指令集,寄存器,寻址模式,存储体系,中断,异常处理以及外部 I/O。指令集架构包含一系列的 opcode 即操作码(机器语言),以及由特定处理器执行的基本命令。
指令集架构常见种类如下:
- 复杂指令集运算(Complex Instruction Set Computing,简称 CISC);
- 精简指令集运算(Reduced Instruction Set Computing,简称 RISC);
- 显式并行指令集运算(Explicitly Parallel Instruction Computing,简称 EPIC);
- 超长指令字指令集运算(VLIW)。
不同的处理器(CPU)使用了大不相同的机器语言,所以我们的程序想要在不同的机器上运行,就需要将源代码根据架构编译成不同的机器语言。
编译原理
Go语言编译器的源代码在 cmd/compile 目录中,目录下的文件共同构成了Go语言的编译器,学过编译原理的人可能听说过编译器的前端和后端,编译器的前端一般承担着词法分析、语法分析、类型检查和中间代码生成几部分工作,而编译器后端主要负责目标代码的生成和优化,也就是将中间代码翻译成目标机器能够运行的机器码。
Go的编译器在逻辑上可以被分成四个阶段:词法与语法分析、类型检查和 AST 转换、通用 SSA 生成和最后的机器代码生成,下面我们来分别介绍一下这四个阶段做的工作。
1) 词法与语法分析
所有的编译过程其实都是从解析代码的源文件开始的,词法分析的作用就是解析源代码文件,它将文件中的字符串序列转换成 Token 序列,方便后面的处理和解析,我们一般会把执行词法分析的程序称为词法解析器(lexer)。
而语法分析的输入就是词法分析器输出的 Token 序列,这些序列会按照顺序被语法分析器进行解析,语法的解析过程就是将词法分析生成的 Token 按照语言定义好的文法(Grammar)自下而上或者自上而下的进行规约,每一个 Go 的源代码文件最终会被归纳成一个 SourceFile 结构:
SourceFile = PackageClause “;” { ImportDecl “;” } { TopLevelDecl “;” }
标准的 Golang 语法解析器使用的就是 LALR(1) 的文法,语法解析的结果其实就是上面介绍过的抽象语法树(AST),每一个 AST 都对应着一个单独的Go语言文件,这个抽象语法树中包括当前文件属于的包名、定义的常量、结构体和函数等。
如果在语法解析的过程中发生了任何语法错误,都会被语法解析器发现并将消息打印到标准输出上,整个编译过程也会随着错误的出现而被中止。
2) 类型检查
当拿到一组文件的抽象语法树 AST 之后,Go语言的编译器会对语法树中定义和使用的类型进行检查,类型检查分别会按照顺序对不同类型的节点进行验证,按照以下的顺序进行处理:
- 常量、类型和函数名及类型;
- 变量的赋值和初始化;
- 函数和闭包的主体;
- 哈希键值对的类型;
- 导入函数体;
- 外部的声明;
通过对每一棵抽象节点树的遍历,我们在每一个节点上都会对当前子树的类型进行验证保证当前节点上不会出现类型错误的问题,所有的类型错误和不匹配都会在这一个阶段被发现和暴露出来。
类型检查的阶段不止会对树状结构的节点进行验证,同时也会对一些内建的函数进行展开和改写,例如 make 关键字在这个阶段会根据子树的结构被替换成 makeslice 或者 makechan 等函数。
其实类型检查不止对类型进行了验证工作,还对 AST 进行了改写以及处理Go语言内置的关键字,所以,这一过程在整个编译流程中是非常重要的,没有这个步骤很多关键字其实就没有办法工作。
3) 中间代码生成
当我们将源文件转换成了抽象语法树,对整个语法树的语法进行解析并进行类型检查之后,就可以认为当前文件中的代码基本上不存在无法编译或者语法错误的问题了,Go语言的编译器就会将输入的 AST 转换成中间代码。
Go语言编译器的中间代码使用了 SSA(Static Single Assignment Form) 的特性,如果我们在中间代码生成的过程中使用这种特性,就能够比较容易的分析出代码中的无用变量和片段并对代码进行优化。
在类型检查之后,就会通过一个名为 compileFunctions 的函数开始对整个Go语言项目中的全部函数进行编译,这些函数会在一个编译队列中等待几个后端工作协程的消费,这些 Goroutine 会将所有函数对应的 AST 转换成使用 SSA 特性的中间代码。
4) 机器码生成
Go语言源代码的 cmd/compile/internal 目录中包含了非常多机器码生成相关的包,不同类型的 CPU 分别使用了不同的包进行生成 amd64、arm、arm64、mips、mips64、ppc64、s390x、x86 和 wasm,也就是说Go语言能够在几乎全部常见的 CPU 指令集类型上运行。
编译器入口
Go语言的编译器入口是 src/cmd/compile/internal/gc 包中的 main.go 文件,这个 600 多行的 Main 函数就是Go语言编译器的主程序,这个函数会先获取命令行传入的参数并更新编译的选项和配置,随后就会开始运行 parseFiles 函数对输入的所有文件进行词法与语法分析得到文件对应的抽象语法树:
func Main(archInit func(*Arch)) {
// …
lines := parseFiles(flag.Args())
接下来就会分九个阶段对抽象语法树进行更新和编译,就像我们在上面介绍的,整个过程会经历类型检查、SSA 中间代码生成以及机器码生成三个部分:
- 检查常量、类型和函数的类型;
- 处理变量的赋值;
- 对函数的主体进行类型检查;
- 决定如何捕获变量;
- 检查内联函数的类型;
- 进行逃逸分析;
- 将闭包的主体转换成引用的捕获变量;
- 编译顶层函数;
- 检查外部依赖的声明;
了解了剩下的编译过程之后,我们重新回到词法和语法分析后的具体流程,在这里编译器会对生成语法树中的节点执行类型检查,除了常量、类型和函数这些顶层声明之外,它还会对变量的赋值语句、函数主体等结构进行检查:
for i := 0; i < len(xtop); i++ {
n := xtop[i]
if op := n.Op; op != ODCL && op != OAS && op != OAS2 && (op != ODCLTYPE || !n.Left.Name.Param.Alias) {
xtop[i] = typecheck(n, ctxStmt)
}
}
for i := 0; i < len(xtop); i++ {
n := xtop[i]
if op := n.Op; op == ODCL || op == OAS || op == OAS2 || op == ODCLTYPE && n.Left.Name.Param.Alias {
xtop[i] = typecheck(n, ctxStmt)
}
}
for i := 0; i < len(xtop); i++ {
n := xtop[i]
if op := n.Op; op == ODCLFUNC || op == OCLOSURE {
typecheckslice(Curfn.Nbody.Slice(), ctxStmt)
}
}
checkMapKeys()
for _, n := range xtop {
if n.Op == ODCLFUNC && n.Func.Closure != nil {
capturevars(n)
}
}
escapes(xtop)
for _, n := range xtop {
if n.Op == ODCLFUNC && n.Func.Closure != nil {
transformclosure(n)
}
}
类型检查会对传入节点的子节点进行遍历,这个过程会对 make 等关键字进行展开和重写,类型检查结束之后并没有输出新的数据结构,只是改变了语法树中的一些节点,同时这个过程的结束也意味着源代码中已经不存在语法错误和类型错误,中间代码和机器码也都可以正常的生成了。
initssaconfig()
peekitabs()
for i := 0; i < len(xtop); i++ {
n := xtop[i]
if n.Op == ODCLFUNC {
funccompile(n)
}
}
compileFunctions()
for i, n := range externdcl {
if n.Op == ONAME {
externdcl[i] = typecheck(externdcl[i], ctxExpr)
}
}
checkMapKeys()
}
在主程序运行的最后,会将顶层的函数编译成中间代码并根据目标的 CPU 架构生成机器码,不过这里其实也可能会再次对外部依赖进行类型检查以验证正确性。
总结
Go语言的编译过程其实是非常有趣并且值得学习的,通过对Go语言四个编译阶段的分析和对编译器主函数的梳理,我们能够对 Golang 的实现有一些基本的理解,掌握编译的过程之后,Go语言对于我们来讲也不再那么神秘,所以学习其编译原理的过程还是非常有必要的。
在Windows上安装Go语言开发包
通过前面几节的学习,相信大家已经对Go语言有了一定的了解,接下来将为大家介绍如何在我们的电脑上安装Go语言开发包,首先从 Windows 系统开始。
下载Go语言开发包
大家可以在Go语言官网(https://golang.google.cn/dl/)下载 Windows 系统下的Go语言开发包,如下图所示。
这里我们下载的是 64 位的开发包,如果读者的电脑是 32 位系统的话,则需要下载 32 位的开发包,在上图所示页面中向下滚动即可找到 32 位开发包的下载地址,如下图所示。
注意:下载 Windows 版本的Go语言开发包时尽量选择 MSI 格式,因为它可以直接安装到系统,不需要额外的操作。
安装Go语言开发包
双击我们下载好的Go语言开发包即可启动安装程序,如下图所示,这是Go语言的用户许可协议,无需管它,直接勾选“I accept …”然后点击“Next”即可。
在 Windows 系统下Go语言开发包会默认安装到 C 盘的 Go 目录下,推荐在这个目录下安装,使用起来较为方便。当然,你也可以选择其他的安装目录,确认无误后点击“Next”,如下图所示:
Go语言开发包的安装没有其他需要设置的选项,点击“Install”即可开始安装,如下图所示:
等待程序完成安装,然后点击“Finish”退出安装程序。
安装完成后,在我们所设置的安装目录下将生成一些目录和文件,如下图所示:
这个目录的结构遵守 GOPATH 规则,后面的章节会提到这个概念。目录中各个文件夹的含义如下表所示。
目录名 | 说明 |
---|---|
api | 每个版本的 api 变更差异 |
bin | go 源码包编译出的编译器(go)、文档工具(godoc)、格式化工具(gofmt) |
doc | 英文版的 Go 文档 |
lib | 引用的一些库文件 |
misc | 杂项用途的文件,例如 Android 平台的编译、git 的提交钩子等 |
pkg | Windows 平台编译好的中间文件 |
src | 标准库的源码 |
test | 测试用例 |
开发时,无须关注这些目录。如果读者希望深度了解底层原理,可以通过上面的介绍继续探索。
设置环境变量
开发包安装完成后,我们还需要配置一下GOPATH
环境变量,之后才可以使用Go语言进行开发。GOPATH 是一个路径,用来存放开发中需要用到的代码包。
在桌面或者资源管理器右键“此电脑”(或者“我的电脑”)→“属性”→“高级系统设置”→“环境变量”,如下图所示。
在弹出的菜单里找到 GOPATH 对应的选项点击编辑之后就可以修改了,没有的话可以选择新建,并将变量名填写为 GOPATH,变量值设置为任意目录均可(尽量选择空目录),例如 D:\Go。
提示:填写完成后,每个打开的窗口都需要点击“确定”来保存设置。
其它的环境变量安装包均会进行自动设置。在默认情况下,Go 将会被安装在目录 c:\go 下,但如果你在安装过程中修改安装目录,则可能需要手动修改所有的环境变量的值。
环境变量设置好后,可以通过go env
命令来进行测试。
C:\Users\Administrator>go env
set GO111MODULE=
set GOARCH=amd64
set GOBIN=
set GOCACHE=C:\Users\Administrator\AppData\Local\go-build
set GOENV=C:\Users\Administrator\AppData\Roaming\go\env
set GOEXE=.exe
set GOFLAGS=
set GOHOSTARCH=amd64
set GOHOSTOS=windows
set GONOPROXY=
set GONOSUMDB=
set GOOS=windows
set GOPATH=C:\Users\Administrator\go
set GOPRIVATE=
set GOPROXY=https://proxy.golang.org,direct
set GOROOT=c:\go
. . .
上面只显示了部分结果,如果执行go env
命令后,出现类似上面的结果,说明我们的Go开发包已经安装成功了。
如果大家想了解更多关于 GOPATH 的介绍可以参考《GOPATH详解》一节。
在Linux上安装Go语言开发包
前面我们介绍了在 Windows 系统上来搭建Go语言开发包,本节将为大家讲解在 Linux 平台安装Go语言开发包,大家可以在Go语言官网找到对应的安装包(https://golang.google.cn/dl/),但是先不要急着下载。
提示:阅读本节需要对 Linux 系统及常用的命令有一定的了解,感兴趣的读者可以通过阅读《Linux入门教程》来了解更多 Linux 相关的知识。
注意:开发包有 32 位和 64 位两个版本,需要根据读者电脑的情况选择不同的版本。
接下来将带领大家一步步的完成安装过程。
安装Go语言开发包
首先,复制Go语言开发包的下载链接(https://dl.google.com/go/go1.13.4.linux-amd64.tar.gz)。然后,在终端使用cd
命令进入你用来存放安装包的目录中(这里使用的是 /usr/local/ 目录,读者也可以使用其它目录)。
root@ububtu:~# cd /usr/local/
root@ububtu:/usr/local#
使用wget
命令下载Go语言开发包,如下所示。
root@ububtu:/usr/local# wget https://dl.google.com/go/go1.13.4.linux-amd64.tar.gz
–2019-11-06 10:47:23-- https://dl.google.com/go/go1.13.4.linux-amd64.tar.gz
正在解析主机 dl.google.com (dl.google.com)… 203.208.39.196, 203.208.39.193, 203.208.39.200, …
正在连接 dl.google.com (dl.google.com)|203.208.39.196|:443… 已连接。
已发出 HTTP 请求,正在等待回应… 200 OK
长度: 120054682 (114M) [application/octet-stream]
正在保存至: “go1.13.4.linux-amd64.tar.gz”
go1.13.4.linux 43% 49.44M 3.56MB/s 剩余 16s ^go1.13.4.linux 100% 114.49M 4.73MB/s 用时 31s
2019-11-06 10:47:56 (3.67 MB/s) - 已保存 “go1.13.4.linux-amd64.tar.gz” [120054682/120054682])
使用tar
命令解压刚刚下载的Go语言开发包。
root@ububtu:/usr/local# tar -C /usr/local -xzf go1.13.4.linux-amd64.tar.gz
解压成功后会在当前目录下新增一个 go 目录,至此我们的Go语言开发包就安装完成了,使用cd
命令进入该目录,然后执行bin/go version
命令就可以查看当前Go语言的版本了。
root@ububtu:/usr/local/go# bin/go version
go version go1.13.4 linux/amd64
配置环境变量
我们需要配置 2 个环境变量分别是 GOROOT 和 PATH。
- GOROOT 的值应该为Go语言的当前安装目录:export GOROOT=/usr/local/go
- PATH 为了方便使用Go语言命令和 Go 程序的可执行文件,需要追加其值:export PATH=
P
A
T
H
:
PATH:
PATH:GOROOT/bin:$GOBIN
为了方便以后的使用,需要把这几个环境变量添加 profile 文件中(~/.bash_profile 或 /etc/profile)。如果是单用户使用,可以将环境变量添加在 home 目录下的 bash_profile 文件中,如果是多用户使用,需要添加在 /etc/profile 文件。(推荐大家在 /etc/profile 文件中设置环境变量)
使用vi /etc/profile
命令打开 profile 文件,并将环境变量添加到文件末尾。
添加完成后使用:wq
命令保存并退出。
然后,使用source /etc/profile
命令使配置文件生效,现在就可以在任意目录使用Go语言命令了。
验证安装
在任意目录下使用终端执行 go env 命令,输出如下结果说明Go语言开发包已经安装成功。
root@ububtu:~$ go env
GO111MODULE=“”
GOARCH=“amd64”
GOBIN=“”
GOCACHE=“/home/feng/.cache/go-build”
GOENV=“/home/feng/.config/go/env”
GOEXE=“”
GOFLAGS=“”
GOHOSTARCH=“amd64”
GOHOSTOS=“linux”
GONOPROXY=“”
GONOSUMDB=“”
GOOS=“linux”
GOPATH=“/home/feng/go”
GOPRIVATE=“”
GOPROXY=“https://proxy.golang.org,direct”
GOROOT=“/usr/local/go”
GOSUMDB=“sum.golang.org”
GOTMPDIR=“”
. . .
提示:上面只显示了部分结果。
在Mac OS上安装Go语言开发包
本节主要为大家讲解如何在Mac OS上安装Go语言开发包,大家可以在Go语言官网下载对应版本的的安装包(https://golang.google.cn/dl/),如下图所示。
安装Go语言开发包
Mac OS 的Go语言开发包是 .pkg 格式的,双击我们下载的安装包即可开始安装。
Mac OS 下是傻瓜式安装,一路点击“继续”即可,不再赘述。
安装包会默认安装在 /usr/local 目录下,如下所示。
安装完成之后,在终端运行 go version
,如果显示类似下面的信息,表明安装成功。
go version go1.13.4 darwin/amd64
设置 GOPATH 环境变量
开始写 go 项目代码之前,需要我们先配置好环境变量。编辑 ~/.bash_profile(在终端中运行 vi ~/.bash_profile
即可)来添加下面这行代码(如果你找不到 .bash_profile,那自己创建一个就可以了)
export GOPATH=$HOME/go
保存然后退出你的编辑器。然后在终端中运行下面命令
source ~/.bash_profile
提示:$HOME 是每个电脑下的用户主目录,每个电脑可能不同,可以在终端运行 echo $HOME 获取
GOROOT 也就是 Go 开发包的安装目录默认是在 /usr/local/go,如果没有,可以在 bash_profile 文件中设置。
export GOROOT=/usr/local/go
然后保存并退出编辑器,运行 source ~/.bash_profile
命令即可。
Go语言集成开发环境(IDE)大汇总
前面我们介绍了Go语言的安装,本节我们来为大家介绍几款强大的Go语言集成开发环境(Integrated Development Environment,IDE)和编辑器。
如何挑选合适的编辑器或集成开发环境呢?下面列举了一些Go语言集成开发环境或编辑器应该具备的特性:
- 语法高亮是必不可少的功能,这也是为什么每个开发工具都提供配置文件来实现自定义配置的原因。
- 拥有较好的项目文件纵览和导航能力,可以同时编辑多个源文件并设置书签,能够匹配括号,能够跳转到某个函数或类型的定义部分。
- 完美的查找和替换功能,替换之前最好还能预览结果。
- 当有编译错误时,双击错误提示可以跳转到发生错误的位置。
- 跨平台,能够在 Linux、Mac OS X 和 Windows 下工作,这样就可以专注于一个开发环境。
- 能够通过插件架构来轻易扩展和替换某个功能。
- 拥有断点、检查变量值、单步执行、逐过程执行标识库中代码的能力。
- 能够方便的存取最近使用过的文件或项目。
- 拥有对包、类型、变量、函数和方法的智能代码补全的功能。
- 能够方便地在不同的 Go 环境之间切换。
- 针对一些特定的项目有项目模板,如:Web 应用,App Engine 项目,从而能够更快地开始开发工作。
下面为大家推荐几款常用的适用于Go语言的编辑器或集成开发环境。
1) Goland
Goland 是由 JetBrains 公司开发的一个新的商业 IDE,旨在为 Go 开发者提供的一个符合人体工程学的新的商业 IDE。Goland 整合了 IntelliJ 平台(一个用于 java 语言开发的集成环境,也可用于其他开发语言),提供了针对Go语言的编码辅助和工具集成。
关于 Goland 的详细使用说明请参考《Goland 2019下载和安装》一节。
2) LiteIDE
LiteIDE是一款专门针对 Go 开发的集成开发环境,在编辑、编译和运行 Go 程序和项目方面都有非常好的支持。同时还包括了对源代码的抽象语法树视图和一些内置工具(此开发环境由国人 vfc 大叔开发)。
LiteIDE 是一款非常好用的轻量级 Go 集成开发环境(基于 QT、Kate 和 SciTE),包含了跨平台开发及其它必要的特性,对代码编写、自动补全和运行调试都有极佳的支持。它采用了 Go 项目的概念来对项目文件进行浏览和管理,它还支持在各个 Go 开发环境之间随意切换以及交叉编译的功能。
同时,它具备了抽象语法树视图的功能,可以清楚地纵览项目中的常量、变量、函数、不同类型以及他们的属性和方法。
关于 LiteIDE 的安装和使用大家可以参考《LiteIDE搭建Go语言开发环境》一节。
3) Sublime Text
一个革命性的跨平台(Linux、Mac OS X、Windows)文本编辑器,它支持编写非常多的编程语言代码。对于 Go 而言,它有一个插件叫做 GoSublime 来支持代码补全和代码模版。
4) GoClipse
是一款 Eclipse IDE 的插件,拥有非常多的特性以及通过 GoCode 来实现代码补全功能。其依附于著名的 Eclipse 这个大型开发环境,虽然需要安装 JVM 运行环境,但却可以很容易地享有 Eclipse 本身所具有的诸多功能。这是一个非常好的编辑器,完善的代码补全、抽象语法树视图、项目管理和程序调试功能。
如果你对集成开发环境都不是很熟悉,那就使用 LiteIDE 吧,另外使用 GoClipse 或者 IntelliJ Idea Plugin 也是不错的选择。
代码补全一般都是通过内置 GoCode 实现的(如:LiteIDE、GoClipse),如果需要手动安装 GoCode,在命令行输入指令 go get -u github.com/nsf/gocode
即可(务必事先配置好 Go 环境变量) 。
5) Visual Studio Code(简称VS Code)
是一款由微软公司开发的,能运行在 Mac OS X、Windows 和 Linux 上的跨平台开源代码编辑器。
VS Code 使用 JSON 格式的配置文件进行所有功能和特性的配置,同时它还可以通过扩展程序为编辑器实现编程语言高亮、参数提示、编译、调试、文档生成等各种功能。
Go语言工程结构详述
一般的编程语言往往对工程(项目)的目录结构是没有什么规定的,但是Go语言却在这方面做了相关规定,本节我们就来聊聊Go语言在工程结构方面的有关知识。
我们前面讲搭建Go语言开发环境时提到的环境变量 GOPATH,项目的构建主要是靠它来实现的。这么说吧,如果想要构建一个项目,就需要将这个项目的目录添加到 GOPATH 中,多个项目之间可以使用;
分隔。
如果不配置 GOPATH,即使处于同一目录,代码之间也无法通过绝对路径相互调用。
目录结构
一个Go语言项目的目录一般包含以下三个子目录:
- src 目录:放置项目和库的源文件;
- pkg 目录:放置编译后生成的包/库的归档文件;
- bin 目录:放置编译后生成的可执行文件。
三个目录中我们需要重点关注的是 src 目录,其他两个目录了解即可,下面来分别介绍一下这三个目录。
src 目录
用于以包(package)的形式组织并存放 Go 源文件,这里的包与 src 下的每个子目录是一一对应。例如,若一个源文件被声明属于 log 包,那么它就应当保存在 src/log 目录中。
并不是说 src 目录下不能存放 Go 源文件,一般在测试或演示的时候也可以把 Go 源文件直接放在 src 目录下,但是这么做的话就只能声明该源文件属于 main 包了。正常开发中还是建议大家把 Go 源文件放入特定的目录中。
包是Go语言管理代码的重要机制,其作用类似于Java中的 package 和 C/C++ 的头文件。Go 源文件中第一段有效代码必须是package <包名>
的形式,如 package hello。
另外需要注意的是,Go语言会把通过go get
命令获取到的库源文件下载到 src 目录下对应的文件夹当中。
pkg 目录
用于存放通过go install
命令安装某个包后的归档文件。归档文件是指那些名称以“.a”结尾的文件。
该目录与 GOROOT 目录(也就是Go语言的安装目录)下的 pkg 目录功能类似,区别在于这里的 pkg 目录专门用来存放项目代码的归档文件。
编译和安装项目代码的过程一般会以代码包为单位进行,比如 log 包被编译安装后,将生成一个名为 log.a 的归档文件,并存放在当前项目的 pkg 目录下。
bin 目录
与 pkg 目录类似,在通过go install
命令完成安装后,保存由 Go 命令源文件生成的可执行文件。在类 Unix 操作系统下,这个可执行文件的名称与命令源文件的文件名相同。而在 Windows 操作系统下,这个可执行文件的名称则是命令源文件的文件名加 .exe 后缀。
源文件
上面我们提到了命令源文件和库源文件,它们到底是什么呢?
- 命令源文件:如果一个 Go 源文件被声明属于 main 包,并且该文件中包含 main 函数,则它就是命令源码文件。命令源文件属于程序的入口,可以通过Go语言的
go run
命令运行或者通过go build
命令生成可执行文件。 - 库源文件:库源文件则是指存在于某个包中的普通源文件,并且库源文件中不包含 main 函数。
不管是命令源文件还是库源文件,在同一个目录下的所有源文件,其所属包的名称必须一致的。
Go语言依赖管理
早期的Go语言被很多开发者所吐槽的一个问题就是没有依赖包的管理,不过随着版本的不断更迭,Go语言依赖管理方面也在不断的完善。
为什么需要依赖管理
最初的时候Go语言所依赖的所有的第三方包都放在 GOPATH 目录下面,这就导致了同一个包只能保存一个版本的代码,如果不同的项目依赖同一个第三方的包的不同版本,应该怎么解决呢?
godep
godep 是一个Go语言官方提供的通过 vender 模式来管理第三方依赖的工具,类似的还有由社区维护的准官方包管理工具 dep。
Go语言从 1.5 版本开始开始引入 vendor 模式,如果项目目录下有 vendor 目录,那么Go语言编译器会优先使用 vendor 内的包进行编译、测试等。
安装godep工具
我们可以通过go get
命令来获取 godep 工具。
go get github.com/tools/godep
命令执行成功后会将 godep 工具的源码下载到 GOPATH 的 src 目录下对应的文件夹中,同时还会在 GOPATH 的 bin 目录下生成一个名为 godep.exe 的可执行文件,如下图所示。
为了方便使用 godep 工具,我们需要将存放 godep.exe 文件的目录添加到环境变量 PATH 中。在系统变量中找到并选中“Path”一行,点击“编辑”按钮,在新弹出的窗口中点击“新建”,然后在最下面一行中填入对应的目录信息。确认无误后点击“确定”。
环境变量设置的打开方式我们在《安装Go语言开发包》一节中已经介绍过了,这里不再赘述。
godep工具的基本命令
完成上面的操作后,我们就可以在命令行窗口(CMD)中使用 godep 工具了,godep 支持的命令如下表所示:
命令 | 作用 |
---|---|
godep save | 将依赖包的信息保存到 Godeps.json 文件中 |
godep go | 使用保存的依赖项运行 go 工具 |
godep get | 下载并安装指定的包 |
godep path | 打印依赖的 GOPATH 路径 |
godep restore | 在 GOPATH 中拉取依赖的版本 |
godep update | 更新选定的包或 go 版本 |
godep diff | 显示当前和以前保存的依赖项集之间的差异 |
godep version | 查看版本信息 |
使用godep help [命令名称]
可以查看命令的帮助信息,如下所示。
C:\Users\Administrator>godep help go
Args: godep go [-v] [-d] command [arguments]
Go runs the go tool with a modified GOPATH giving access to
dependencies saved in Godeps.
Any go tool command can run this way, but “godep go get”
is unnecessary and has been disabled. Instead, use
“godep go install”.
If -v is given, verbose output is enabled.
If -d is given, debug output is enabled (you probably don’t want this, see -v).
使用godep工具
执行godep save
命令,会在当前目录中创建 Godeps 和 vender 两个文件夹。Godeps 文件夹下会生成一个 Godeps.json 文件,用来记录项目中所依赖的包信息;vender 目录则是用来保存当前项目所依赖的所有第三方包。
生成的 Godeps.json 文件的结构如下所示:
{
“ImportPath”: “main”,
“GoVersion”: “go1.13”,
“GodepVersion”: “v80”,
“Deps”: [
{
“ImportPath”: “github.com/go-gomail/gomail”,
“Comment”: “2.0.0-23-g81ebce5”,
“Rev”: “81ebce5c23dfd25c6c67194b37d3dd3f338c98b1”
}
]
}
其中,“ImportPath”为项目的路径信息,“GoVersion”为Go语言的版本号,“GodepVersion”为 godep 工具的版本号,“Deps”为当前依赖包的路径、版本号信息等等。
提示:当引用的第三方包要升级时,只需要修改 Godep.json 里面的依赖包的版本号,然后再次执行 godep save 命令即可。
godep 工具的主要功能就是控制Go语言程序编译时依赖包搜索路径的优先级。例如查找项目的某个依赖包,首先会在项目根目录下的 vender 文件夹中查找,如果没有找到就会去 GOAPTH/src 目录下查找。
go module
go module 是Go语言从 1.11 版本之后官方推出的版本管理工具,并且从 Go1.13 版本开始,go module 成为了Go语言默认的依赖管理工具。
GO111MODULE
在Go语言 1.12 版本之前,要启用 go module 工具首先要设置环境变量 GO111MODULE,不过在Go语言 1.13 及以后的版本则不再需要设置环境变量。通过 GO111MODULE 可以开启或关闭 go module 工具。
- GO111MODULE=off 禁用 go module,编译时会从 GOPATH 和 vendor 文件夹中查找包。
- GO111MODULE=on 启用 go module,编译时会忽略 GOPATH 和 vendor 文件夹,只根据 go.mod 下载依赖。
- GO111MODULE=auto(默认值),当项目在 GOPATH/src 目录之外,并且项目根目录有 go.mod 文件时,开启 go module。
Windows 下开启 GO111MODULE 的命令为:
set GO111MODULE=on 或者 set GO111MODULE=auto
MacOS 或者 Linux 下开启 GO111MODULE 的命令为:
export GO111MODULE=on 或者 export GO111MODULE=auto
在开启 GO111MODULE 之后就可以使用 go module 工具了,也就是说在以后的开发中就没有必要在 GOPATH 中创建项目了,并且还能够很好的管理项目依赖的第三方包信息。
使用 go module 的go mod init
命令后会在当前目录下生成一个 go. mod 文件,并且在编译/运行当前目录下代码或者使用go get
命令的时候会在当前目录下生成一个 go.sun 文件。
go.mod 文件记录了项目所有的依赖信息,其结构大致如下:
module main.go
go 1.13
require (
github.com/astaxie/beego v1.12.0
github.com/shiena/ansicolor v0.0.0-20151119151921-a422bbe96644 // indirect
)
其中,module 为 go.mod 文件所属的包,require 为项目所依赖的包及版本号,indirect 表示间接引用。
go.sum 文件则是用来记录每个依赖包的版本及哈希值,如下所示。
github.com/Knetic/govaluate v3.0.0+incompatible/go.mod h1:r7JcOSlj0wfOMncg0iLm8Leh48TZaKVeNIfJntJ2wa0=
github.com/OwnLocal/goes v1.0.0/go.mod h1:8rIFjBGTue3lCU0wplczcUgt9Gxgrkkrw7etMIcn8TM=
github.com/astaxie/beego v1.12.0 h1:MRhVoeeye5N+Flul5PoVfD9CslfdoH+xqC/xvSQ5u2Y=
github.com/astaxie/beego v1.12.0/go.mod h1:fysx+LZNZKnvh4GED/xND7jWtjCR6HzydR2Hh2Im57o=
常用的 go mod 命令如下表所示:
命令 | 作用 |
---|---|
go mod download | 下载依赖包到本地(默认为 GOPATH/pkg/mod 目录) |
go mod edit | 编辑 go.mod 文件 |
go mod graph | 打印模块依赖图 |
go mod init | 初始化当前文件夹,创建 go.mod 文件 |
go mod tidy | 增加缺少的包,删除无用的包 |
go mod vendor | 将依赖复制到 vendor 目录下 |
go mod verify | 校验依赖 |
go mod why | 解释为什么需要依赖 |
GOPROXY
proxy 顾名思义就是代理服务器的意思。大家都知道,国内的网络有防火墙的存在,这导致有些Go语言的第三方包我们无法直接通过go get
命令获取。GOPROXY 是Go语言官方提供的一种通过中间代理商来为用户提供包下载服务的方式。要使用 GOPROXY 只需要设置环境变量 GOPROXY 即可。
目前公开的代理服务器的地址有:
- goproxy.io
- goproxy.cn:(推荐)由国内的七牛云提供。
Windows 下设置 GOPROXY 的命令为:
go env -w GOPROXY=https://goproxy.cn,direct
MacOS 或 Linux 下设置 GOPROXY 的命令为:
export GOPROXY=https://goproxy.cn
Go语言在 1.13 版本之后 GOPROXY 默认值为 https://proxy.golang.org,在国内可能会存在下载慢或者无法访问的情况,所以十分建议大家将 GOPROXY 设置为国内的 goproxy.cn。
使用go get命令下载指定版本的依赖包
执行go get
命令,在下载依赖包的同时还可以指定依赖包的版本。
- 运行
go get -u
命令会将项目中的包升级到最新的次要版本或者修订版本; - 运行
go get -u=patch
命令会将项目中的包升级到最新的修订版本; - 运行
go get [包名]@[版本号]
命令会下载对应包的指定版本或者将对应包升级到指定的版本。
提示:go get [包名]@[版本号]
命令中版本号可以是 x.y.z 的形式,例如 go get foo@v1.2.3,也可以是 git 上的分支或 tag,例如 go get foo@master,还可以是 git 提交时的哈希值,例如 go get foo@e3702bed2。
第一个Go语言程序
通过前面学习大家已经对Go语言有了一定的了解,那要怎么来创建一个Go语言程序呢?本节就来带领大家实现一个简单的程序——在控制台输出“Hello World!”。
在控制台输出“Hello World!”非常简单,仅需要几行代码就可以搞定,如下所示:
package main // 声明 main 包
import (
“fmt” // 导入 fmt 包,打印字符串是需要用到
)
func main() { // 声明 main 主函数
自我介绍一下,小编13年上海交大毕业,曾经在小公司待过,也去过华为、OPPO等大厂,18年进入阿里一直到现在。
深知大多数Go语言工程师,想要提升技能,往往是自己摸索成长或者是报班学习,但对于培训机构动则几千的学费,着实压力不小。自己不成体系的自学效果低效又漫长,而且极易碰到天花板技术停滞不前!
因此收集整理了一份《2024年Go语言全套学习资料》,初衷也很简单,就是希望能够帮助到想自学提升又不知道该从何学起的朋友,同时减轻大家的负担。
既有适合小白学习的零基础资料,也有适合3年以上经验的小伙伴深入学习提升的进阶课程,基本涵盖了95%以上Golang知识点,真正体系化!
由于文件比较大,这里只是将部分目录大纲截图出来,每个节点里面都包含大厂面经、学习笔记、源码讲义、实战项目、讲解视频,并且后续会持续更新
如果你觉得这些内容对你有帮助,可以添加V获取:vip1024b (备注Go)
一个人可以走的很快,但一群人才能走的更远。不论你是正从事IT行业的老鸟或是对IT行业感兴趣的新人,都欢迎扫码加入我们的的圈子(技术交流、学习资源、职场吐槽、大厂内推、面试辅导),让我们一起学习成长!
ttps://goproxy.cn
Go语言在 1.13 版本之后 GOPROXY 默认值为 https://proxy.golang.org,在国内可能会存在下载慢或者无法访问的情况,所以十分建议大家将 GOPROXY 设置为国内的 goproxy.cn。
使用go get命令下载指定版本的依赖包
执行go get
命令,在下载依赖包的同时还可以指定依赖包的版本。
- 运行
go get -u
命令会将项目中的包升级到最新的次要版本或者修订版本; - 运行
go get -u=patch
命令会将项目中的包升级到最新的修订版本; - 运行
go get [包名]@[版本号]
命令会下载对应包的指定版本或者将对应包升级到指定的版本。
提示:go get [包名]@[版本号]
命令中版本号可以是 x.y.z 的形式,例如 go get foo@v1.2.3,也可以是 git 上的分支或 tag,例如 go get foo@master,还可以是 git 提交时的哈希值,例如 go get foo@e3702bed2。
第一个Go语言程序
通过前面学习大家已经对Go语言有了一定的了解,那要怎么来创建一个Go语言程序呢?本节就来带领大家实现一个简单的程序——在控制台输出“Hello World!”。
在控制台输出“Hello World!”非常简单,仅需要几行代码就可以搞定,如下所示:
package main // 声明 main 包
import (
“fmt” // 导入 fmt 包,打印字符串是需要用到
)
func main() { // 声明 main 主函数
自我介绍一下,小编13年上海交大毕业,曾经在小公司待过,也去过华为、OPPO等大厂,18年进入阿里一直到现在。
深知大多数Go语言工程师,想要提升技能,往往是自己摸索成长或者是报班学习,但对于培训机构动则几千的学费,着实压力不小。自己不成体系的自学效果低效又漫长,而且极易碰到天花板技术停滞不前!
因此收集整理了一份《2024年Go语言全套学习资料》,初衷也很简单,就是希望能够帮助到想自学提升又不知道该从何学起的朋友,同时减轻大家的负担。
[外链图片转存中…(img-cIT82MJ3-1712998765100)]
[外链图片转存中…(img-RqGr4BAI-1712998765103)]
[外链图片转存中…(img-FVeUdXzy-1712998765103)]
[外链图片转存中…(img-sLTykirX-1712998765104)]
[外链图片转存中…(img-G1HWfO1q-1712998765104)]
既有适合小白学习的零基础资料,也有适合3年以上经验的小伙伴深入学习提升的进阶课程,基本涵盖了95%以上Golang知识点,真正体系化!
由于文件比较大,这里只是将部分目录大纲截图出来,每个节点里面都包含大厂面经、学习笔记、源码讲义、实战项目、讲解视频,并且后续会持续更新
如果你觉得这些内容对你有帮助,可以添加V获取:vip1024b (备注Go)
[外链图片转存中…(img-OneoWX52-1712998765105)]
一个人可以走的很快,但一群人才能走的更远。不论你是正从事IT行业的老鸟或是对IT行业感兴趣的新人,都欢迎扫码加入我们的的圈子(技术交流、学习资源、职场吐槽、大厂内推、面试辅导),让我们一起学习成长!