这是我参与「第五届青训营 」伴学笔记创作活动的第 2 天
Go 语言进阶 - 工程进阶
课程代码:[Moonlight-Zhao/go-project-example at V0 (github.com)](https://github.com/Moonlight-Zhao/go-project-example/tree/V0)
并发编程
并发与并行
Goroutine
- 线程:内核态,线程跑多个协程,栈 MB 级别,比较消耗资源
- 协程:用户态,栈 KB 级别,可以理解为轻量级的线程
Goroutine 是Go中最基本的组织单位之一。事实上,每个Go程序至少拥有一个:main goroutine,当程序开始时会自动创建并启动。在几乎所有Go程序中,你都可能会发现自己迟早加入到一个goroutine中。
简单来说,goroutine 是一个并发的函数和其他代码一起运行,可使用关键词go来创建一个goroutine。
上图说明:使用for循环执行五次go func() {} ,每个go func() {}创建了一个goroutine,形成五个hello并发执行,以达到快速打印五次指定内容,返回结果如下图:
CSP (Communicating Sequential Processes)
此处提倡通过通信共享内存而不是通过共享内存而实现通信,在 Go 中 Goroutine 则是通过某一通道实现共享内存,而这一通道则是管道:Channel
Channel
Go 中通过 make(chan 元素类型, [缓冲大小])定义管道,其中又通过是否定义缓冲大小决定这个管道是有缓冲通道还剩无缓冲通道
并发安全 Lock
在高并发场景下,进程、线程(协程)可能会发生资源竞争,导致数据脏读、脏写、死锁等问题,为了避免此类问题的发生,就有了并发安全。
此处举例使用互斥锁实现并发安全:
互斥锁的作用就是在代码上创建一个临界区,保证串行操作(同一时间只有一个 goroutine 执行临界区代码)。
返回结果:
WaitGroup
goroutine是基于主线程的一个细小的分支,我们要跑完这个分支,就只能在主线程还未结束时才能跑,一旦主线程跑路了,我们这个小分支失去了依靠,那么就会挂掉!这时就需要一个人出来拦一拦,而WaitGroup正好就是充当这个角色。
依赖管理
Go 依赖管理演进
- 不同环境(项目)依赖的版本不同
- 控制依赖库的版本
- GOPATH
- Go Vendor
- Go Module
GOPATH
-
环境变量
$GOPATH- bin:项目编译的二进制文件
- 项目编译的中间产物,加速编译
- 项目源码
-
项目代码直接依赖 src 目录下的代码
-
go get 下载最新版本的包到 src 目录下
-
缺点:无法实现 package 的多版本控制
Go Vendor
-
项目目录下增加 vendor 文件,所有依赖包副本形式放在
$ProjectRoot/vendor -
依赖寻址方式:vendor -> GOPATH
-
通过每个项目引入一份依赖的副本,解决了多个项目需要同一个 package 依赖的冲突问题
-
缺点:
- 无法控制依赖的版本
- 更新项目又可能出现依赖冲突,导致编译出错
Go Module
- 通过 go.mod 文件管理依赖包版本
- 通过 go get/go mod 指令工具管理依赖包
- 能够定义版本规则和通过工具管理项目依赖关系
依赖管理三要素
- 配置文件,描述依赖:
go.mod - 中心仓库管理依赖库:
Proxy - 本地工具:
go get/mod
工具 - go get
-
go get example.org/pkg
- @update:默认
- @none:删除依赖
- @v1.1.2:tag 版本,语义版本
- @23dfdd5:特定的 commit
- @master:分支的最新 commit
-
go mod
- init:初始化,创建 go.mod 文件
- download:下载模块到本地缓冲
- tidy:增加需要的依赖,删除不需要的依赖
测试
- 回归测试
- 集成测试
- 单元测试
单元测试
- 所有测试文件以
_test.go结尾 - 测试函数为
func TestXxx(*testing.T) - 初始化逻辑放到
TestMain中
- 一般覆盖率:50%~60%,较高覆盖率80%+
- 测试分支相互独立、全面覆盖
- 测试单元粒度足够小,函数单一职责
单元测试 - Mock
组件包:monkey : https://github.com/bouk/monkey
基准测试
- 优化代码,需要对当前代码分析
- 内置的测试框架提供了基准测试的能力
- 测试函数为
BenchmarkXxx(b *testing.B)