这是我参与「第五届青训营 」笔记创作活动的第2天
Day2
并发编程
goroutine简单来说就是轻量级线程,也称为协程。协程是用户态轻量级线程,创建切换等因为其处于用户态,不需要系统调用,且其栈空间小,占用资源少。总的来说,线程是因为进程过于笨重而出现的,而协程相对于线程又更减少了资源占用以及切换创建等操作的开销。
协程通信:提倡通过通信共享内存,但也能通过共享内存实现通信。通信共享内存通过通道实现,内存共享实现通信则必须加临界区。
src := make(chan int)
dest := make(chan int, 3)
go func() {
defer close(src)
for i := 0; i < 10; i++ {
src <- i
}
}()
channel创建通常使用make函数。对于chan来说,其自带同时性,在通道内没有数据时输出会造成堵塞,但在通道关闭时再从通道中取值会取出零值。在使用通道时应该考虑数据进出的速度,从而设置合适的缓冲区大小。
GO中的并发安全也通过lock对临界区进行保护实现。对于加锁的临界区,在同一时刻仅有一个协程能够访问执行。
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()
}
主进程往往无法知道各协程执行情况,而有时我们必须等待所有协程执行任务完毕再结束主进程,因此可以使用WaitGroup来实现。WaitGroup实际是一个计数器,可以令其初始值为协程个数,在每个协程执行完毕时对其进行+1操作,最终为零时主进程可以结束。
依赖管理
Go依赖管理三阶段,目前主要使用Go Module。
bin:项目编译的二进制文件
pkg:项目编译中间产物,加速编译
src:项目源码
Gopath:项目代码直接依赖src下的代码,go get下载最新版本包到src
弊端:无法实现pkg的多版本控制
Govendor:项目目录增加vendor文件夹,项目需要引入时优先从vendor中引入。
弊端:无法控制依赖版本,更新项目有可能出现依赖冲突,导致编译出错
问题本质:还是依赖源码,版本概念不清晰
Go Module:通过go.mod管理依赖包版本, 通过go get/go mod指令管理依赖包。
语义化版本:v 1(Major).3(Minor).0(Patch),不同Major版本隔离,不同Minor向下兼容
直接从依赖管理网站拉代码会使代码构建不稳定,原作者随时会修改甚至删除依赖包代码,且这样会对代码管理网站造成高负载压力。因此采用适配器模式,用一个proxy去缓存下载过的包,大家都从proxy出获取依赖包。
测试
测试可分为回归测试、集成测试、单元测试。覆盖率逐层变大,成本逐层降低
GO单元测试规则:
- 所有测试文件以_test.go结尾
- func TestXXX(*testing.T),即函数以Test开头
- 初始化逻辑放到TestMain中
代码覆盖率是指,至少执行了一次的条目数占整个条目数的百分比。如果"条目数"是语句,对应的就是行覆盖率。如果"条目数"是函数,对应的就是函数覆盖率。如果"条目数"是路径,对应的就是路径覆盖率。
- 一般应实现50-60%的覆盖率,较高要求应达到80%以上
- 测试分支相互独立全面覆盖
- 测试单元粒度足够小
弊端:单元测试会有外部依赖,难以在测试时实现幂等性和稳定性。
解决方法:使用mock测试,即用一个自定义普通函数替代原函数,以达到结构目的,使测试不再依赖原本项目中的一些包或者数据。