Skip to content

简介

https://golang.org/

https://github.com/golang/go

https://golang.google.cn/

https://golang.google.cn/pkg/

https://pkg.go.dev/

Go语言中文网

https://github.com/avelino/awesome-go

https://github.com/jobbole/awesome-go-cn

https://github.com/yinggaozhen/awesome-go-cn

https://play.golang.org/
https://goplay.tools/

https://tour.go-zh.org/
https://tour.golang.org

Go 学习之路
Go 夜读
Go社区的知识图谱
Gopher China
Go 开发者路线图

The Uber Go Style Guide.

Go 语言在多核并发上拥有原生的设计优势。
Go 语言从 2009 年 11 月开源,2012 年发布 Go 1.0 稳定版本以来,已经拥有活跃的社区和全球众多开发者,并且与苹果公司的 Swift 一样,称为当前非常流行的开发语言之一。

很多公司,特别是中国的互联网公司,即将或者已经完成了使用 Go 语言改造旧系统的过程。
经过 Go 语言重构的系统能使用更少的硬件资源而有更高的并发和 I/O 吞吐表现

Go 语言简单易学,学习曲线平缓,不需要像 C/C++ 语言动辄需要两到三年的学习期。
Go 语言被称为“互联网时代的 C 语言”。

互联网的短、频、快特性在 Go 语言中体现得淋漓尽致。
一个熟练的开发者只需要短短的一周时间就可以从学习阶段转到开发阶段,并完成一个高并发的服务器开发。

Go 语言是一门相对比较年轻的语言,不仅得益于其优秀的设计,同时借鉴了现有多种成熟语言的丰富经验,使得 Go 语言成为历史上发展最为迅速的编程语言之一。
尤其在云原生领域,由 Docker、Kubernetes 为代表的大批开源明星项目大幅推动了 Go 语言的发展,乃至当前 CNCF(Cloud Native Computing Foundation,云原生计算基金会)旗下的绝大部分开源项目都是以 Go 语言为主要编程语言。
如同近十年 AI 尤其是深度学习神经网络的高速发展将 Python 语言推进至 "人工智能领域的首选编程语言" 一样,我们可以毫不夸张的说,Go 语言已经成为"云原生领域的首选编程语言"

Go 语言以语法特性精炼、开发效率高著称,并且非常重视工程化能力和规范性,比如严苛的语法格式、内置测试能力等。
但是,作为一门系统级编程语言,并且内置了自动垃圾回收、协程、通道、接口、元编程等高级能力的语言,各种语言陷阱与高级使用问题仍是 Go 语言开发者不可忽视的内容。
尤其对于使用 Go 语言构建基础设施平台、基础云服务、高性能中间件、高负载服务端等关键业务的开发者,更加需要重视 Go 语言的高级编程知识。
从 Go 语言的多种常用数据结构如切片、结构体、字符串、映射表、管道等的使用上,到迭代器、循环、接口、协程、并发锁、异常处理等常见逻辑处理上,以及性能调优、测试、包管理、编译等工程步骤上,都会涉及各种可能会导致编码漏洞并进而引发业务风险的问题。

电子书/教程

《Go语言圣经(中文版)》
http://www.gopl.io/
https://github.com/adonovan/gopl.io/

《Go语言高级编程》 出版时间: 2019-7-9 豆瓣评分8.1(134人)
https://github.com/golang-china/gopl-zh

《Go语言四十二章经》
《Go语言编程之旅》

https://docs.microsoft.com/zh-cn/learn/paths/go-first-steps/

安装更新

https://golang.org/dl/

直接下载安装包(例如go1.15.8.darwin-amd64.pkg),然后双击按指示安装

1
2
➜  ~ go version
go version go1.13.4 darwin/amd64
1
2
3
4
5
6
7
8
$ go env
...
GOOS="darwin"
GOPATH="/Users/nocilantro/go"
...
GOROOT="/usr/local/go"
GOTOOLDIR="/usr/local/go/pkg/tool/darwin_amd64"
...

更新时重新下载新的安装包安装即可

1
2
$ go version
go version go1.15.8 darwin/amd64

mac 安装更新

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
#if you using the default go version specified by brew
brew upgrade go

#if you using a specific version from the brew
brew unlink go@youversion (e.g.: brew unlink go@1.17)
brew install go



brew uninstall go@1.19
brew install go

ubuntu 安装

首先下载好 go1.16.6.linux-amd64.tar.gz

1
sudo tar -xz -C /usr/local -f go1.16.6.linux-amd64.tar.gz

~/.zshrc 中添加如下内容:

1
export PATH=$PATH:/usr/local/go/bin
1
2
3
$ source ~/.zshrc
$ go version
go version go1.16.6 linux/amd64

centos 安装

1
2
wget https://go.dev/dl/go1.21.6.linux-amd64.tar.gz
tar -xz -C /usr/local -f go1.21.6.linux-amd64.tar.gz

在 ~/.zshrc 中添加如下内容:

export PATH=$PATH:/usr/local/go/bin

1
2
3
$ source ~/.zshrc
$ go version
go version go1.21.6 linux/amd64

Go 语言特性

Go 语言是 Google 公司开发的一种静态型、编译型并且自带垃圾回收和并发的编程语言

Go 语言的风格类似于 C 语言。其语法在 C 语言的基础上进行了大幅的简化,去掉了不需要的表达式括号,循环也只有 for 一种表示方法,就可以实现数值、键值等各种遍历。因此,Go 语言上手非常容易

Go 语言最有特色的特性莫过于 goroutine。Go 语言在语言层可以通过 goroutine 对函数实现并发执行。
goroutine 类似于线程但是并非线程,goroutine 会在 Go 语言运行时进行自动调度。
因此,Go 语言非常适合用于高并发网络服务的编写

上手容易

从实现一个 HTTP 服务器开始了解

HTTP 文件服务器是常见的 Web 服务之一。开发阶段为了测试,需要自行安装 Apache 或 Nginx 服务器,下载安装配置需要大量的时间

使用 Go 语言实现一个简单的 HTTP 服务器只需要几行代码

httpserver.go

1
2
3
4
5
6
7
8
package main

import ("net/http")

func main() {
    http.Handle("/", http.FileServer(http.Dir(".")))
    http.ListenAndServe(":8080", nil)
}
  • 第 1 行,标记当前文件为 main 包,main 包也是 Go 程序的入口包
  • 第 3 行,导入 net/http 包,这个包的作用是 HTTP 的基础封装和访问
  • 第 5 行,程序执行的入口函数main()
  • 第 6 行,使用http.FileServer文件服务器将当前目录作为根目录("/")的处理器,访问根目录,就会进入当前目录
  • 第 7 行,默认的 HTTP 服务侦听在本机 8080 端口。

在终端执行命令:go run httpserver.go

然后在浏览器里输入http://127.0.0.1:8080/即可浏览文件,这些文件正是当前目录在 HTTP 服务器上的映射目录

编译输出可执行文件

Go 语言的代码可以直接输出为目标平台的原生可执行文件。
Go 语言不使用虚拟机,只有运行时(runtime)提供垃圾回收和 goroutine 调度等

Go 语言使用自己的链接器,不依赖任何系统提供的编译器、链接器。
因此编译出的可执行文件可以直接运行在几乎所有的操作系统和环境中

从 Go 1.5 版本之后,Go 语言实现自举,实现了使用 Go 语言编写 Go 语言编译器及所有工具链的功能

htlloworld.go

1
2
3
4
5
6
7
package main

import "fmt"

func main() {
    fmt.Println("hello world")
}

确认安装 Go 语言的开发包,使用如下指令可以将这段代码编译为可执行文件:go build helloworld.go

执行helloworld可执行文件:./helloworld即可输出hello world

来讨论下程序本身。Go 语言的代码通过(package)组织,包类似于其它语言里的库(libraries)或者模块(modules)。
一个包由位于单个目录下的一个或多个 .go 源代码文件组成,目录定义包的作用。
每个源文件都以一条package声明语句开始,这个例子里就是package main,表示该文件属于哪个包,紧跟着一系列导入(import)包,之后是存储在这个文件里的程序语句。

Go 的标准库提供了 100 多个包,以支持常见功能,如输入、输出、排序以及文本处理。比如fmt包,就含有格式化输出、接收输入的函数。
Println 是其中一个基础函数,可以打印以空格间隔的一个或多个值,并在最后添加一个换行符,从而输出一整行。

main 包比较特殊。它定义了一个独立可执行的程序,而不是一个库,在 main 里的 main 函数也很特殊,它是整个程序执行时的入口。
main 函数所做的事情就是程序做的。当然了,main 函数一般调用其它包里的函数完成很多工作,比如 fmt.Println

Go 语言不仅可以输出可执行文件,还可以编译输出能导入 C 语言的静态库、动态库

同时从 Go1.7 版本开始,Go 语言支持将代码编译为插件。使用插件可以动态加载需要的模块,而不是一次性将所有的代码编译为一个可执行文件

工程结构简单

Go 语言的源码无须头文件,编译的文件都来自于后缀名为 go 的源码文件;Go 语言无须解决方案、工程文件和 MakeFile。
只要将工程文件按照 GOPATH 的规则进行填充,即可使用 go build/go install 进行编译,编译安装的二进制可执行文件统一放在 bin 文件夹下

编译速度快

Go 语言可以利用自己的特性实现并发编译,并发编译的最小元素是包。
从 Go 1.9 版本开始,最小并发编译元素缩小到函数,整体编译速度提高了 20%

另外,Go 语言语法简单,具有严谨的工程结构设计、没有头文件、不允许包的交叉依赖等规则,在很大程度上加速了编译的过程

Go 语言使用了更加智能的编译器,并简化了解决依赖的算法,最终提供了更快的编译速度。
编译 Go 程序时,编译器只会关注那些直接被引用的库,而不是像 Java、C 和 C++ 那样,要遍历依赖链中所有依赖的库。
因此,很多 Go 程序可以在 1 秒内编译完。
在现代硬件上,编译整个 Go 语言的源码树只需要 20 秒

因为没有从编译代码到执行代码的中间过程,用动态语言编写应用程序可以快速看到输出。
代价是,动态语言不提供静态语言提供的类型安全特性,不得不经常用大量的测试套件来避免在运行的时候出现类型错误这类 bug

想象一下,使用类似 JavaScript 这种动态语言开发一个大型应用程序,有一个函数期望接收一个叫做 ID 的字段。
这个参数应该是整数,是字符串,还是一个 UUID?要想知道答案,只能去看源代码。
可以尝试使用一个数字或者字符串来执行这个函数,看看会发生什么。
在 Go 语言里,完全不用为这件事情操心,因为编译器就能帮用户捕获这种类型错误。

高性能

国外的一个编程语言性能测试网

https://benchmarksgame-team.pages.debian.net/benchmarksgame/index.html

这个网站可以对常见的编程语言进行性能比较,网站使用都是最新的语言版本和常见的一些算法

可以看出,Go 语言在性能上更接近于 Java 语言,但毕竟 Java 语言已经经历了多年的积累和优化。
Go 语言在未来的版本中会通过不断的版本优化提高单核运行性能

原生支持并发

Go 语言的特性就是从语言层原生支持并发,无须第三方库、开发者的编程技巧及开发经验就可以轻松地在 Go 语言运行时来帮助开发者决定怎么使用 CPU 资源

Go 语言的并发是基于 goroutine,goroutine 类似于线程,但并非线程。
可以将 goroutine 理解为一种虚拟线程。
Go 语言运行时会参与调度 goroutin,并将 goroutine 合理地分配到每个 CPU 中,最大限度地使用 CPU 性能

多个 goroutine 中,Go 语言使用通道(channel)进行通信,程序可以将需要并发的程序设计为生产者和消费者的模式,将数据放入通道

通道的另外一端的代码将这些数据进行并发计算并返回结果

goroutine

goroutine 是可以与其他 goroutine 并行执行的函数,同时也会与主程序(程序的入口)并行执行。
在其他编程语言中,你需要用线程来完成同样的事情,而在 Go 语言中会使用同一个线程来执行多个 goroutine。
例如,用户在写一个 Web 服务器,希望同时处理不同的 Web 请求,如果使用 C 或者 Java,不得不写大量的额外代码来使用线程。
在 Go 语言中,net/http 库直接使用了内置的 goroutine。
每个接收到的请求都自动在其自己的 goroutine 里处理。
goroutine 使用的内存比线程更少,Go 语言运行时会自动在配置的一组逻辑处理器上调度执行 goroutien。
每个逻辑处理器绑定到一个操作系统线程上。这让用户的应用程序执行效率更高,而开发工作量显著减少。

如果想在执行一段代码的同时,并行去做另外一些事情,goroutine 是很好的选择。
下面是一个简单的例子

1
2
3
4
5
6
func log(msg string) {
    ... 这里是一些记录日志的代码
}

// 代码里有些地方检测到了错误
go log("发生了可怕的事情")

关键字 go 是唯一需要去编写的代码,调度 log 函数作为独立的 goroutine 去运行,以便与其他 goroutine 并行执行。
这意味着应用程序的其余部分会与记录日志并行执行,通常这种并行能让最终用户觉得性能更好。
就像之前说的,goroutine 占用的资源更少,所以常常能启动成千上万个 goroutine

通道

通道是一种数据结构,可以让 goroutine 之间进行安全的数据通信。
通道可以帮用户避免其他语言里常见的共享内存访问的问题。

并发的最难的部分就是要确保其他并发运行的进程、线程或 goroutine 不回意外修改用户的数据。
当不同的线程在没有同步保护的情况下修改同一个数据时,总会发生灾难。
在其他语言中,如果使用全局变量或者共享内存,必须使用复杂的锁规则来防止对同一个变量的不同步修改。

为了解决这个问题,通道提供了一种新模式,从而保证并发修改时的数据安全。
通道这一模式保证同一时刻只会有一个 goroutine 修改数据。
通道用于在几个运行的 goroutine 之间发送数据。

性能分析

安装 Go 语言开发包后,使用 Go 语言工具链可以直接进行 Go 代码的性能分析。
Go 的性能分析工具将性能数据以二进制文件输出,配合 Graphviz 即可将性能分析数据以图形化的方式展现出来

强大的标准库

Go 语言的标准库覆盖网络、系统、加密、编码、图形等各个方面,可以直接使用标准库的 http 包进行 HTTP 协议的收发处理;
网络库基于高性能的操作系统通信模型(Linux 的 epoll、Windows 的 IOCP);
所有的加密、编码都内建支持,不需要再从第三方开发者处获取。

Go 语言的编译器也是标准库的一部分,通过词法器扫描源码,使用语法树获得源码逻辑分支等。

Go 语言的周边工具也是建立在这些标准库上。在标准库上可以完成几乎大部分的需求

代码风格清晰、简单

Go 语言写起来类似 C 语言,因此熟悉 C 语言及其派生语言 C++ 的人都会迅速熟悉这门语言

C 语言的有些语法会让代码可读性降低甚至发生歧义。
Go 语言在 C 语言的基础上取其精发,弃其糟粕,将 C 语言中较为容易发生错误的写法进行调整,作出相应的编译提示

(1) 去掉冗余括号
Go 语言在众多大师的丰富实战经验的基础上诞生,去除了 C 语言语法中的一些冗余、繁琐的部分

1
2
3
4
// C 语言的 for 数值循环
for (int i = 0; i < 10; i++) {
    // 循环代码
}

在 Go 语言中,这样的循环变为

1
2
3
for i := 0; i < 10; i++ {
    // 循环代码
}

for 两边的括号被去掉,int 声明被简化为 ":=",直接通过编译器右值推导获得 i 的变量类型并声明

(2) 去掉表达式冗余括号

同样的简化也可以在判断语句中体现出来

1
2
3
4
// C 语言
if (表达式) {
    // 表达式成立
}

在 Go 语言中,无须添加表达式括号,

1
2
3
4
// Go 语言
if 表达式 {
    // 表达式成立
}

(3) 强制的代码风格

Go 语言中,左括号必须紧接着语句不换行。
其他样式的括号将被视为代码编译错误。
这个特性刚开始会使开发者有一些不习惯,但随着对 Go 语言的不断熟悉,开发者就会发现风格统一让大家在阅读代码时把注意力集中到了解决问题上,而不是代码风格上

(4) 不再纠结于 i++ 和 ++i

在 Go 语言中,自增操作符不再是一个操作符,而是一个语句。
因此,在 Go 语言中自增只有一种写法

1
i++

如果写成前置自增 "++i",或者赋值后自增 "a=i++",都将导致编译错误

国内镜像

https://learnku.com/go/wikis/38122

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
在 Linux 或 macOS 上面,需要运行下面命令(或者,可以把以下命令写到 .bashrc 或 .bash_profile 文件中):

# 启用 Go Modules 功能
go env -w GO111MODULE=on

# 配置 GOPROXY 环境变量,以下三选一

# 1. 七牛 CDN
go env -w  GOPROXY=https://goproxy.cn,direct

# 2. 阿里云
go env -w GOPROXY=https://mirrors.aliyun.com/goproxy/,direct

# 3. 官方
go env -w  GOPROXY=https://goproxy.io,direct

确认一下:

1
2
$ go env | grep GOPROXY
GOPROXY="https://goproxy.cn"

windows

在 Windows 上,需要运行下面命令:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
# 启用 Go Modules 功能
$env:GO111MODULE="on"

# 配置 GOPROXY 环境变量,以下三选一

# 1. 七牛 CDN
$env:GOPROXY="https://goproxy.cn,direct"

# 2. 阿里云
$env:GOPROXY="https://mirrors.aliyun.com/goproxy/,direct"

# 3. 官方
$env:GOPROXY="https://goproxy.io,direct"

测试一下

1
$ time go get golang.org/x/tour

类型系统

Go 语言提供了灵活的、无继承的类型系统,无需降低运行性能就能最大程度上复用代码。
这个类型系统依然支持面向对象开发,但避免了传统面向对象的问题。
如果你曾经在复杂的 Java 和 C++ 程序上花数周时间考虑如何抽象类和接口,你就能意识到 Go 语言的类型系统有多么简单。
Go 开发者使用组合(composition)设计模式,只需简单地将一个类型嵌入到另一个类型,就能复用所有的功能。
其他语言也能使用组合,但是不得不和继承绑在一起使用,结果使整个用法非常复杂,很难使用。
在 Go 语言中,一个类型由其他更微小的类型组合而成,避免了传统的基于继承的模型。

另外,Go 语言还具有独特的接口实现机制,允许用户对行为进行建模,而不是对类型进行建模。
在 Go 语言中,不需要声明某个类型实现了某个接口,编译器会判断一个类型的实例是否符合正在使用的接口。
Go 标准库里的很多接口都非常简单,只开放几个函数。
从实践上讲,尤其对那些使用类似 Java 的面向对象语言的人来说,需要一些时间才能习惯这个特性。

类型简单

Go 语言不仅有类似 int 和 string 这样的内置类型,还支持用户定义的类型。
在 Go 语言中,用户定义的类型通常包含一组带类型的字段,用于存储数据。
Go 语言的用户定义的类型看起来和 C 语言的结构很像,用起来也很相似。
不过 Go 语言的类型可以声明操作该类型数据的方法。
传统语言使用继承来扩展结构--Client 继承自 User,User 继承自 Entity,Go 语言与此不同,Go 开发者构建更小的类型 -- Customer 和 Admin,然后把这些小类型组合成更大的类型。

Go 接口对一组行为建模

接口用于描述类型的行为。
如果一个类型的实例实现了一个接口,意味着这个实例可以执行一组特定的行为。
你甚至不需要去声明这个实例实现某个接口,只需要实现这组行为就好。
其他的语言把这个特性叫做鸭子类型--如果它叫起来像鸭子,那它就可能是只鸭子。
Go 语言的接口也是这么做的。在 Go 语言中,如果一个类型实现了一个接口的所有方法,那么这个类型的实例就可以存储在这个接口类型的实例中,不需要额外声明。

在类似 Java 这种严格的面向对象语言中,所有的设计都围绕接口展开。
在编码前,用户经常不得不思考一个庞大的继承链。下面是一个 Java 接口的例子

1
2
3
4
interface User {
    public void login();
    public void logout();
}

在 Java 中要实现这个接口,要求用户的类必须满足 User 接口里的所有约束,并且显式声明这个类实现了这个接口。
而 Go 语言的接口一般只会描述一个单一的动作。
在 Go 语言中,最常使用的接口之一是 io.Reader。这个接口提供了一个简单的方法,用来声明一个类型有数据可以读取。
标准库内的其他函数都能理解这个接口。这个接口的定义如下:

1
2
3
type Reader interface {
    Read(p []byte)(n int, err error)
}

为了实现 io.Reader 这个接口,你只需要实现一个 Read 方法,这个方法接受一个 byte 切片,返回一个整数和可能出现的错误。

这和传统的面向对象编程语言的接口系统有本质的区别。
Go 语言的接口更小,只倾向于定义一个单一的动作。实际使用中,这更有利于使用组合来复用代码。
用户几乎可以给所有包含数据的类型实现 io.Reader 接口,然后把这个类型的实例传给任意一个知道如何读取 io.Reader 的 Go 函数。

Go 语言的整个网络库都使用了 io.Reader 接口,这样可以将程序的功能和不同网络的实现分离。
这样的接口用起来有趣、优雅且自由。文件、缓冲区、套接字以及其他的数据源都实现了 io.Reader 接口。
使用同一个接口,可以高效地操作数据,而不用考虑到底数据来自哪里。

内存管理

不当的内存管理会导致程序崩溃或者内存泄漏,甚至让整个操作系统崩溃。
Go 语言拥有现代化的垃圾回收机制,能帮你解决这个难题。
在其他系统语言(如 C 或者 C++)中,使用内存前要先分配这段内存,而且使用完毕后要将其释放掉。
哪怕只做错了一件事,都可能导致程序崩溃或者内存泄漏。
可惜,追踪内存是否还被使用本事就是十分艰难的事情,而要想支持多线程和高并发,更是让这件事难上加难。
虽然 Go 语言的垃圾回收会有一些额外的开销,但是编程时,能显著降低开发难度。
Go 语言把无趣的内存管理交给专业的编译器来做,而让程序员专注于更有趣的事情。

使用 Go 语言的项目

Docker 项目
Kubernetes 项目

leetcode

https://github.com/halfrost/LeetCode-Go

https://github.com/aQuaYi/LeetCode-in-Go

https://github.com/kylesliu/awesome-golang-algorithm