Go内存泄漏分析及解决办法
Mon ,Jun 5 ,2017文章将展示常见的Goroutine leak以及对应的解决办法。
Talk is cheap, I will show you the code.
各种泄露的展示
type Writer struct {
queue chan []byte
}
func NewWriter() *Writer {
w := &Writer{
queue: make(chan []byte, 10),
}
go w.process()
return w
}
func (w *Writer) Write(message []byte) {
w.queue <- message
}
func (w *Writer) process() {
for {
message := <- w.queue
// do something with message
}
}
func main() {
fmt.Println(runtime.NumGoroutine())
test()
fmt.Println(runtime.NumGoroutine())
}
func test() {
NewWriter()
}
test中执行的NewWriter消失在了上下文里,Go程则存在于后台,造成了泄露。
package main
import (
"fmt"
"math/rand"
"time"
)
func queryFromSrc(src string) (ret string) {
time.Sleep(1000)
ret = fmt.Sprintf("query done")
return ret
}
func multiQuery() (ret string) {
res := make(chan string)
go func() {
temp := make(chan int)
num := <- temp
fmt.Printf("get num %d \n",num)
}()
go func() {
res <- queryFromSrc("ns2.dnsserver.com")
}()
return "hello"
}
func main() {
fmt.Println("start multi query:")
res := multiQuery()
fmt.Println("res=", res)
}
上述的代码一共有两处导致内存泄露的部分:
第18行:从一个没有输入的管道中阻塞读取数据
第22行:向一个没有接收的管道中写入数据阻塞。
以上都会造成内存泄漏,总结可以得出: > * Go程想从一个通道读数据,但通道没有写入数据。 > * Go程想往一个通道写数据,但是这个通道没有接收方,该Go程将阻塞无法向下执行 > * 综合上述两种情况,如本文的第一段代码,Go程内形成闭环,与外部隔绝。
解决办法
1.增加管道缓存
上述第二段代码中,第22行的问题,如果,15行的代码变成res := make(chan string, 1),则不会写阻塞。最终数据会写入缓存管道,依靠Gc回收。
2.增加读超时处理
针对第18行的问题,展示一种超时处理:
go func() {
defer done(0)
temp := make(chan int)
select {
case num := <- temp: //正常的接收
fmt.Printf("%d \n",num)
case <- time.After(1) : //触发了超时
fmt.Printf("time done stop 0 \n")
}
}()
3.生产者close生产管道,在消费的Go程内部使用for range形式,遇到close会跳出循环。
4.全局的关闭管道, 可以做错误处理,下文的select, 当在外部因为任何原因关闭 in 时, select中in 会被触发。
package main
import (
"fmt"
"time"
"runtime"
)
func test(in <- chan int, i int) {
// 模拟的是close 关闭的情况
for my := range in {
fmt.Printf(" %d is shut down \n", my)
}
fmt.Printf("out %d is shut down \n", i)
/*
// 模拟外部关闭的情况,in此时可以看作统一的退出管道。
select {
case <- in:
fmt.Printf(" %d is shut down \n", i)
return
}
*/
}
func main() {
in := make(chan int)
for i := 0; i < 10; i++ {
go test(in, i)
}
fmt.Println(runtime.NumGoroutine())
close(in)
time.Sleep(10000)
fmt.Println(runtime.NumGoroutine())
}