1.golang进阶
1.1 golang的异常处理
golang中defer、panic、recover是很常用的三个特性,三者一起使用可以充当其他语言中try…catch…的角色,而defer本身又像其他语言的析构函数。
1.1.1 defer
defer后边会接一个函数调用,函数调用,函数调用!!!,但该函数不会立刻被执行,而是等到包含它的程序返回时(包含它的函数执行了return语句、运行到函数结尾自动返回、对应的goroutine panic)defer函数才会被执行。通常用于资源释放、打印日志、异常捕获等
如果有多个defer函数,调用顺序类似于栈,越后面的defer函数越先被执行(后进先出)
1 | func main() { |
output:
1 | 4 |
defer的深入理解:
return xxx并不是一条原子指令,含有defer函数的外层函数,返回的过程是这样的:
1 | 返回值 = xxx |
example 1:
1 | func f() (result int) { |
example 2:
1 | func f() (r int) { |
example 3:
1 | func f() (r int) { |
defer函数的参数值,是在声明defer时确定下来的
-
函数参数
-
闭包引用
在defer函数声明时,对外部变量的引用有两种方式:作为函数参数和作为闭包引用
作为函数参数,在defer申明时就把值传递给defer,并将值缓存起来,调用defer的时候使用缓存的值进行计算(如上边的example 3)而作为闭包引用,在defer函数执行时根据整个上下文确定当前的值
1 | func main() { |
output:
1 | c: 1 |
1.1.2 recover要与defer配对使用,并且不跨协程,才能真正拦截panic
-
panic
只会触发当前 Goroutine 的defer
; -
recover
只有在defer
中调用才会生效; -
panic
允许在defer
中嵌套多次调用;
2.golang的依赖管理
GO语言的依赖管理经历了三个阶段:
-
GOPATH
-
vendor
-
go mod (终极解决方案)
GOPATH的弊端
-
没有版本控制概念
-
无法同步一致的第三方版本号
-
无法指定当前项目的版本号
于是go mod 就诞生了
GOPATH目录下有bin、pkg、src目录,go1.11之前需要将项目强制放在$GOPATH/src目录下,有了go module的概念后,项目可以放在任何位置。
2.1 go mod 命令管理模块依赖
go mod命令
1 | go mod init 生成go.mod文件 |
2.2 What is a module?
A module is a collection of related Go packages that are versioned together as a single unit.
-
一个仓库包含若干个module
-
一个module包含若干个package
-
一个package包含若干个xxx.go文件
3.GO的常见环境变量
To show documentation for all go env variables and values you can run:
1 | go help environment |
GO111MODULE (GO1.11版本)
GO Modules是go语言的依赖解决方案,发布于
Go1.11
,成长于Go1.12
,丰富于Go1.13
,正式于Go1.14
推荐在生产中使用。Go Modules的出现解决了Go1.11版本之前几个常见的争议问题:
Go语言长期以来的依赖管理问题
“淘汰”现有的GOPATH模式
统一社区中的其他依赖管理工具
Go语言提供了GO111MODULE这个环境变量作为Go modules的开关,允许设置如下值:
-
auto: 只要项目包含了go.mod文件就启动Go modules
-
on: 启用Go modules,推荐设置
-
off: 禁用Go modules,不推荐设置
1 | go env -w GO111MODULE=on |
Go Modules 的语义化版本控制
版本号递增规则如下:
主版本号: 做了
不兼容的API修改
次版本号: 做了
向下兼容的功能性新增
修订号: 做了
向下兼容的问题修正
GOPATH
是用户工作空间目录的环境变量,属于用户域的范畴,自Go1.11
之后已经不推荐使用,
实质上它是个目录
1 | $go env |
可以看出GOPATH变量的结果是一个路径,进入该路径继续查看,目录结构如下
1 | go |
-
bin: 存储编译生成的二进制文件
-
pkg: 存储预编译的目标文件,以加快程序后续的编译速度
-
src: 存储所有的
.go
文件或源代码
为什么弃用GOPATH?
GOPATH模式下
没有版本控制
的概念,通常会造成以下问题:
在执行
go get
时,无法传达任何的版本信息,因为它是以目录路径作为划分的。在运行Go应用程序时,无法保证其他人与我们所期望依赖的第三方库的版本是同一个。
无法处理v1、v2、v3等不同版本的引用问题,因为在GOPATH模式下,导入路径是一样的,例如都是
github.com/foo/bar
。
第三方版本库全局缓存在$GOPATH/pkg/mod
和$GOPATH/pkg/sumdb
目录下,要清除全局缓存可以使用go clean -modcache
命令
1 | $go clean -modcacache |
GOPROXY
从哪里拉取第三方包
其默认值为: https://proxy.golang.org,direct
1 | go env -w GOPROXY=https://goproxy.cn,direct |
GOPRIVATE
1 | go env -w GOPRIVATE="git.example.com,github.com/vancholen/hello" |
4.iota
-
itoa 是自动增长的
-
itoa 的这个增长是在一个"=" 作用域结束之后
-
iota是golang语言的常量计数器,只能在常量的表达式中使用。
iota在const关键字出现时将被重置为0(const内部的第一行之前),const中每新增一行常量声明将使iota计数一次(iota可理解为const语句块中的行(“行块”
apple, banana = iota + 1, iota + 2
)索引)。
1 | // itoa 是自动增长的 |
5.函数闭包实现类似数据库自增功能
1 | // 函数闭包,子函数内部可以读取父函数作用域的变量 |
6. 使用goroutine提升用户体验
1 | // 使用goroutine提升用户体验 |
7.关闭 channel的动作由写端发起——channel 实现数字传递游戏
关闭channel的动作一定要由写端发起!!! (谁负责写channel,谁就负责关闭channel)
关闭channel的动作一定要由写端发起!!!
关闭channel的动作一定要由写端发起!!!
1 | // 3个goroutine 数字传递游戏 |
8.“主线程”中等待所有goroutine执行完成
Golang 官方在 sync
包中提供了 WaitGroup
类型可以解决这个问题。其文档描述如下:
使用方法可以总结为下面几点:
-
在父协程中创建一个
WaitGroup
实例,比如名称为:wg -
调用
wg.Add(n)
,其中 n 是等待的goroutine
的数量 -
在每个
goroutine
运行的函数中执行defer wg.Done()
-
调用
wg.Wait()
阻塞主逻辑 -
直到所有
goroutine
执行完成。
使用sync.WaitGroup
完善示例7:channel实现数字传递游戏
的代码:
1 | package main |
9.The Go Compiler
go tool compile main.go
==> main.o
go tool compile -pack main.go
==> main.a
go tool compile -S main.go
==> main.o和汇编代码
10. Calling C Code from Go
10.1 Calling C code from Go using the same file
1 | package main |
10.2 Calling C code from Go using separate files
callC.h
1 |
|
callC.c
1 |
|
编译命令
gcc -c *.c
ar rx callC.a *.o
main.go
1 | package main |
编译命令
go build -o main main.go
11.Calling Go functions from C code
userdByC.go
文件:
1 | package main |
使用go build -o usedByC.o -buildmode=c-shared usedByC.go
编译后会生成usedByC.h
和usedByC.o
两个文件
main.c
文件内容:
1 |
|
12. Type Assertion
A type assertion is the x.(T)
notation where x
is of interface type and T
is a type.
1 | package main |
1 | package main |
13. Go交叉编译
编译命令: env GOOS=windows GOARCH=amd64 go build xxx.go
环境变量GOOS
和GOARCH
支持的值到如下网址查看:
https://go.dev/doc/install/source
14.Go 代码测试
testing
标准包的使用
命令:go test xxx.go xxx_test.go -run='F1'
-
xxx.go
中含有要被测试的函数实现,譬如有个函数f1
,xxx_test.go
中的内容是测试代码,对应应该有Testf1(t *testing.T)
-
-run=F1
表示只想测试那些函数名中有F1
的那些函数
15.Benchmarking Go Code(基准测试)
Benchmarking can give you information about the performance of a function or a program in order to understand better how much faster or slower a function is compared to another function, or compared to the rest of the application.
A function signature like func (*tesing.B)
will be invoked automatically by the go test
command
16.解析config.json 配置文件
核心代码:json.NewDecoder(file).Decode(baseCfg)
1 | package main |
config.json
:
1 | { |
17.import导入的是包名?文件夹名?
答案:是路径文件夹名!!!
golang的import还支持如下两种方式来加载自己写的模块:
-
相对路径
import "./model"
//当前文件同一目录的model目录,但是不建议这种方式import -
绝对路径
import "shorturl/model"
//加载GOPATH/src/shorturl/model模块
GO总是先从GOROOT
这个环境变量先搜索,再从GOPATH
列出的路径顺序中搜索,只要一搜索到合适的文件夹
就理解停止。当搜索完了仍搜索不到包时,将报错。
包导入后,就可以使用这个包中的属性。使用包名.属性的方式即可。
-
import导入的参数是路径文件夹名,而非包名。
-
尽管习惯将包名和文件夹名保持一致,但这并不是强制要求。
-
.go源文件
中引用包成员时,使用包名,而非路径名 -
包名和和文件夹名有可能不一样,但为了更好的维护和更高的可读性,普遍的做法是包名和目录名一致,如若不一致,import的时候要写文件夹名,引用的时候要写包名。
-
同一文件夹下,所有
.go文件
必须使用相同的包名称(因为导入时使用绝对路径,所以在搜索路径下,包必须有唯一路径,但无须是唯一名字);
当发现VS Code
提示golang中的某些包找不到(具体表现为有红色下划波浪线),请使用VS Code
打开xxx.mod
所在的文件夹。
18.Viewing Go Documentation
You can view documentation for the standard library packages via your terminal using the go doc
tool. I often use this during development to quickly check something — like the name or signature of a specific function. I find it faster than navigating the web-based documentation and it’s always available offline too.
1 | $ go doc strings # View simplified documentation for the strings package |
You can also include the -src flag to display the relevant Go source code. For example:
1 | $ go doc -src strings.Replace # View the source code for the strings.Replace function |
19.Channel Communication
-
A send on a channel is synchronized before the completion of the corresponding receive from that channel.
-
The closing of a channel is synchronized before a receive that returns a zero value because the channel is closed.
-
A receive from an unbuffered channel is synchronized before the completion of the corresponding send on that channel.
-
The kth receive on a channel with capacity C is synchronized before the completion of the k+Cth send from that channel completes.
题目1😉
1 | package main |
输出 hello,gopher!
题目2😉
1 | package main |
输出 hello,gopher!
题目3😉
1 | package main |
// It might print the empty string, crash, or do something else.
题目4😉
1 | package main |
输出hello,gopher!
题目5😉
1 | package main |
输出hello,gopher!