Go语言进阶 | 青训营笔记

52 阅读3分钟

这是我参与「第五届青训营 」笔记创作活动的第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单元测试规则:

  1. 所有测试文件以_test.go结尾
  2. func TestXXX(*testing.T),即函数以Test开头
  3. 初始化逻辑放到TestMain中

代码覆盖率是指,至少执行了一次的条目数占整个条目数的百分比。如果"条目数"是语句,对应的就是行覆盖率。如果"条目数"是函数,对应的就是函数覆盖率。如果"条目数"是路径,对应的就是路径覆盖率。

  • 一般应实现50-60%的覆盖率,较高要求应达到80%以上
  • 测试分支相互独立全面覆盖
  • 测试单元粒度足够小

弊端:单元测试会有外部依赖,难以在测试时实现幂等性和稳定性。

解决方法:使用mock测试,即用一个自定义普通函数替代原函数,以达到结构目的,使测试不再依赖原本项目中的一些包或者数据。