layout | title | tags |
---|---|---|
post |
Go 语言开发之 Goroutine 泄露检测 |
Go |
Go 语言本身提供内置关键字支持并发,也就是 goroutine 和 channel 的支持,使得使用 Go 语言进行多线程/协程开发非常容易。用语言描述可能不够直接,我们直接来写一个简单的并发程序,从我们比较熟悉的爬虫入手,大家应该都写过吧?
简单的爬虫无非就是下载网页和处理网页结果两个部分,如果顺序执行的话比较耗时的是网络下载网页的过程,所以我们需要把下载网页的部分进行异步化。
func main() {
var wg sync.WaitGroup
ch := make(chan string, 1)
var urls = []string{"xxx", "yyy", "zzz"}
for _, u := range urls {
wg.Add(1)
// 并发处理耗时的 IO 操作,比如爬虫场景
go func(url string) {
defer wg.Done()
time.Sleep(time.Duration(rand.Intn(3)) * time.Second)
ch <- url // 回传结果
}(u)
}
// 逐个处理异步操作回传回来的结果
go func() {
wg.Add(1)
defer wg.Done()
for _, _ = range urls {
select {
case i := <-ch:
fmt.Printf("处理:%s\n", i)
}
}
}()
wg.Wait()
fmt.Println("所有操作执行完成")
}
整体来看上面代码就两部分,一部分异步去处理耗时的 IO 操作,第二部分处理回传的结果,整体都是并发执行的,所以执行结果的输出是不确定的。
不知道大家看懂了没有,可以看到使用关键字 go 能够很容易的去异步执行一个函数。
在我的观念里面,只要容易的事情,肯定就会出现滥用情况,所以 goroutine 泄露就会变得很常见,同时也是很难排查的问题。
说了那么多,今天要推荐的项目就是要解决 goroutine 泄露的问题,大厂 uber 出品:goleak,通过在单元测试执行之后检测堆栈中是否还存在尚未结束的 goroutine 来判断是否存在泄露,可以很好的集成到我们的项目代码里面。如下是两个示例:
func TestA(t *testing.T) { // 单个函数添加
defer goleak.VerifyNone(t)
// test logic here.
}
func TestMain(m *testing.M) { // 整个 main 程序添加,从顶层逐步向下检测
goleak.VerifyTestMain(m)
}
是不是很简单?使用方式跟正常执行单元测试一样,项目的代码也不多同时非常简洁,感兴趣的小伙伴可以阅读一下,更多详情可以通过如下链接了解。