青训营 x Go语言工程实践
这是第二篇青训营的笔记了,继续跟着课程走,这次的课程相比第一天的基础更贴近开发场景了,质量还是非常不错的。
高性能编程
现代计算机CPU基本采用多核心架构,但是现在许多程序依然采用单线程模式开发,因为多线程开发十分考验开发者的技术,并且开发繁琐,go语言被称为"为并发而生"的语言降低了开发者进行多线程编程的技术门槛。
并发 vs 并行
并发:多线程程序在一个CPU内核上交替执行
并行:多线程程序在多个CPU内核上同时执行
Go作为一门新兴的编程语言,在诞生之初就考虑了多核运行,提供了简单的并发编程方法,可以说是为并发而生的。Go实现并发的重要工具就是协程。
协程
协程可以认为是运行在用户态的轻量级线程(协程的栈一般为KB级别,线程的栈是MB级别),省去了在内核态和用户态之间切换的开销。
Go中创建一个协程只需要在函数前加一个go关键字,即可开启一个协程。
//开启协程
go func(...) {
...
}
Go提倡通过通信共享内存取代通过共享内存实现通信,而go实现这一目的的方法就是:通道(channel)。channel根据有无缓冲,可以分为无缓冲通道和有缓冲通道:
//创建无缓冲通道
make(chan int)
//创建有缓冲通道
make(chan int, 2)
Go同样提供了共享内存进行通信的方法,但是不保证并发安全,需要自行加锁。
WaitGroup是go提供的阻塞方法,在协程完成前阻塞程序。
var wg sync.WaitGroup
wg.Add(delta int) //阻塞计时器增加
wg.Done() //阻塞计时器减1
wg.Wait() //阻塞直到计时器为0
使用WaitGroup可以帮助开发者轻松完成多线程任务的等待,而不需要等待特定时间。
依赖管理
依赖管理是项目开发绕不过的话题,go在升级迭代中也诞生了多个依赖管理方式:GOPATH、GO Vendor、Go Module
GOPATH存在多版本控制问题,假如不同项目依赖不同版本的某个package,无法实现package共存。Go Vendor为了解决这个问题而诞生,通过为项目引入依赖副本,解决了问题;编译器会先在vendor目录下寻找依赖,找不到的话会去GOPATH下寻找;但是虽然避免了冲突问题,却不能很好地控制版本,更新某个项目的特定依赖。
Go Module与1.11版本引入,在1.16后成为默认方法,是目前最为常用,也最推荐使用的依赖管理方法。使用上有点Maven的味道。
一些需要注意的:当项目的不同依赖依赖另外某个依赖的不同版本时,go Module会选择最低兼容版本(即满足所有的依赖要求的最低版本)
本地开发中主要会用到的就是go get/mod命令:
go mod init #初始化,创建go.mod
go mod download #下载模块到本地缓存
go mod tidy #整理依赖,增加需要的,清除不需要的
go get example.org/pkg@version #获取指定版本的依赖
测试
软件上线前的测试是避免安全事故的重要保障,是软件开发周期的不可缺少的一环。测试可以分为回归测试、集成测试和单元测试,三种测试的覆盖率依次增大,成本却依次减少。
单元测试
单元测试简单来说就是对指定的测试单元输入测试样例,针对输出和期望输出进行对比,有点像在OJ上刷题的感觉。单元测试起到了保证质量、提升效率的重要作用。
go中对于单元测试有一些规范要求。所有测试文件要以_test.go结尾,例如对于beta.go,其测试文件就是beta_test.go;测试函数的命名规则是func TestXxx(t *testing.T) ;测试文件的初始化逻辑要求放在TestMain()函数中。
func TestMain(m *testing.M) {
//测试前:数据装载、配置初始化等前置工作
code := m.Run()
//测试后:释放资源等收尾操作
os.Exit(code)
}
评估单元测试质量的一个标注是代码覆盖率,通过在go test指令后添加--cover参数可以显示测试中的代码覆盖率。
对于依赖某些本地文件或变量的测试,可以使用Mock替换具有外部依赖的函数,保障测试的一致性。
基准测试
基准(benchmark)测试与单元测试不同,主要是为了测试程序的性能而非正确性。基准测试的使用方法和单元测试很像,但是测试函数的命名规则是func BenchmarkXxx(b *testing.B)。
go test指令默认不会运行benchmark用例,需要加上-bench参数。
在进行基准测试时,需要考虑到初始化等准备工作也需要消耗时间,所以应该在准备工作之后使用b.ResetTimer()来重置计时器;另一种方法是使用StratTimer()和StopTimer()来计时,这种方法的一个好处是可以清除关闭资源等工作带来的额外时间开销的影响。
PS. -bench允许接收正则参数来决定运行哪些样例,这点十分使用;此外-benchtime和-count参数可以用于提升测试的精准度(通过多次执行或增加测轮数)。
小结
并行开发一直是一个难题,但go为我们提供了简单的解决方案,在开发中要多加利用。测试可能对于个人学习者而言,接触的机会不多,但确实在项目开发中十分重要的一环,需要认真学习。
坚持学习,日日进步!