这是我参与「第五届青训营 」伴学笔记创作活动的第 2 天
go进阶
高性能go语言
Goroutine
- 协程:用户态,轻量级线程
- 线程:内核态,线程跑多个协程
go func(j int) {
hello(j)
}(i)
CSP(communicating sequential processes)
提倡通过通信共享内存, 而不是通过共享内存通信
Channel
make(chan 元素类型, [缓冲大小])
- 无缓冲: make(chan int) 导致两个goroutine同步化
- 有缓冲: make(chan int, 2)
实例:
func calSquare() {
src := make(chan int)
dest := make(chan int, 3)
go func() {
defer close(src)
for i := 0; i < 10; i++ {
src <- i
}
}()
go func() {
defer close(dest)
for i := range src {
dest <- i*i
}
}()
for i := range dest {
fmt.Println(i)
}
}
用通信实现共享内存
并发安全Lock
var lock sync.Mutex
lock.Lock()
...
lock.Unlock()
WaitGroup
Add(delta int)计数器加deltaDone()计数器减1Wait()阻塞直到计数器为0
对于多个协程, 可以设置对应数量的计数器, 结束时计数器减一, 直到全部结束, 实现阻塞的效果
func manygowait() {
var wg sync.WaitGroup
wg.Add(5)
for i:= 0; i<5; i++ {
go func(j int) {
defer wg.Done()
hello(j)
}(i)
}
wg.Wait()
}
依赖管理
go依赖演进
GOPATH->go vendor->go module
GOPATH:
- 环境变量
$GOPATH中 - 目录bin: 存放编译的二进制文件
- 目录pkg: 存放编译的中间文件, 加速编译
- 目录src: 存放项目源码
- 弊端: 多个项目依赖一个包的不同版本, 无法实现package的多版本控制
Go Vender:
- 在项目目录下增加vendor目录, 优先查找vendor中的依赖包, 再查找
$GOPATH - 弊端: 无法控制依赖的版本
Go Module:
- 通过go.mod文件管理依赖包版本
- 通过
go get或go mod指令管理依赖包
依赖管理三要素
- 配置文件描述依赖: go.mod
- 中心仓库管理依赖库: proxy
- 本地工具: go get/mod
依赖配置-go.mod
- 语义化版本:
${MAJOR}.${MINOR}.${PATCH} - 基于commit伪版本:
vx.0.0-{时间戳}-{hash code} // indirect: 标识非直接依赖+incompatible: 由于go module是1.11版本提出的, 要求主版本2+的模块要在路径后增加/vN后缀. 对于之前已经打上2以上版本的仓库, 要在版本号后加上这个后缀
依赖分发-proxy
在代码仓库之间加入一层proxy服务站点, 缓存依赖的软件, 可以增加依赖的稳定性
-
变量GOPROXY
环境变量
GOPROXY="url1, url2, direct". 标识服务站点url的列表, direct标识源站, 当其它站点都没有时从源站获取
工具-go get/mod
go测试
回归测试, 集成测试, 单元测试(覆盖率变大, 成本降低)
单元测试
针对函数, 模块等的测试, 将数据输入到测试单元, 将输出和期望做比较
规则:
-
所有测试文件以
_test.go结尾 -
func TestXxx(*testing.T)func TestHellotom(t *testing.T) { output := hellotom() expectOutput := "tom" if output != expectOutput { t.Errorf("some error") } } -
初始化逻辑放到
TestMainfunc TestMain(m *testing.M) { // 测试前工作 code := m.Run() // 测试后工作 os.Exit(code) }
可以使用assert包进行判断
assert.Equal(t, output, expectOutput)
覆盖率--评估单元测试
- 衡量代码是否经过了足够测试
- 评价项目的测试水准
- 评估项目是否达到了高水准测试等级
go test judgment_test.go judgment.go --cover
判断标准: 测试过程中代码的执行比例
对各个分支都进行测试, 保证覆盖率
一般覆盖率: 50%-60%;
测试分支相互独立, 全面覆盖
测试单元粒度足够小, 函数单一职责
mock--解决单元测试依赖问题
单元测试需要依赖外部文件,数据库,redis等, 测试结果需要满足幂等性和稳定性, 用mock可以解决外部数据不一致的问题
使用monkey库的patch方法进行打桩, 将调用外部数据的函数替换为另一个, 拥有确定的输出
基准测试
测试代码性能
规则: func Benchmarkxxx(b *testing.B)
func BenchmarkSelect(b *testing.B) {
InitServerIndex() // 初始化工作不需要纳入测试范围
b.ResetTimer()
for i := 0; i < b.N; i++ {
Select()
}
}
func BenchmarkSelectParallel(b *testing.B) {
InitServerIndex() // 初始化工作不需要纳入测试范围
b.ResetTimer()
b.RunParallel(func(pb *testing.PB) {
for pb.Next() {
Select()
}
})
}