Go 语言开发之 Goroutine 泄露检测

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)
}

是不是很简单?使用方式跟正常执行单元测试一样,项目的代码也不多同时非常简洁,感兴趣的小伙伴可以阅读一下,更多详情可以通过如下链接了解。

项目地址:https://github.com/uber-go/goleak


更多精彩请扫码关注如下公众号。

Written on January 15, 2020