分析goroutine是否泄漏
- 从pprof的goroutine分析,是否是goroutine在持续增长。如果持续增长。goroutine肯定泄漏
列
package main
import (
"net/http"
_ "net/http/pprof"
"time"
)
type none struct{}
func main() {
go func() {
ch := make(chan none)
consumer(ch)
producer(ch)
}()
_ = http.ListenAndServe("0.0.0.0:8080", nil)
}
func consumer(ch chan none) {
for i := 0; i < 1000; i++ {
// 此处类似协程泄漏
go func() {
<-ch
}()
time.Sleep(3 * time.Microsecond)
}
}
func producer(ch chan none) {
time.Sleep(100 * time.Second)
for i := 0; i < 1000; i++ {
ch <- none{}
}
}
上述代码中,逐步创建了1k个goroutine(假定是泄漏的),我们可以通过http://127.0.0.1:8080/debug/pprof/ 访问查看goroutine的变化情况。
- 在debug 中观察goroutine的数量变化,如果持续增长,那可以确定是goroutine 泄漏了。
- 之后访问 http://127.0.0.1:8080/debug/pprof/goroutine?debug=1查看各goroutine数量,查看持续增加的goroutine ,如果存在持续增长的goroutine,那从goroutine的堆栈代码短分析即可。
数据泄漏
- 数据泄漏出现的问题比较多。比如长的string,slice 数据用切片的方式被引用,如果切片后的数据不释放,长的string,slice 是不会释放的,当然这种泄漏比较小
package main
import (
"fmt"
"io/ioutil"
"net"
"net/http"
_ "net/http/pprof"
"time"
)
type None int64
func main() {
go func() {
singals := []int64{}
netListen, _ := net.Listen("tcp", ":30000")
defer netListen.Close()
for {
conn, err := netListen.Accept()
if err != nil {
fmt.Println("Accept Error")
}
singals = append(singals, 1)
go doSomething(conn)
}
for _ = range singals {
fmt.Println("Received")
}
}()
_ = http.ListenAndServe("0.0.0.0:8080", nil)
}
func doSomething(conn net.Conn) {
defer conn.Close()
time.Sleep(100 * time.Microsecond)
buf, err := ioutil.ReadAll(conn)
if err == nil {
fmt.Println(string(buf))
}
}
例子比较简单,从net Accept 数据,并开启一个goroutine 做数据处理。singals 呢,用于做事件处理,每接收一个链接,给singal 推一条数据。
为了从中查找内存泄漏,我们也增加了pprof。
为了能尽快发现问题,我这边用了一个简单的shell对服务施压(请求2w http 服务,不关心请求返回结果)。命令如下:
seq 0 20000
从pprof 的 heap 中,我们能轻易的发现:
内存分配中,mem_leak文件的26行(append) 操作 申请的内存排在了top 1,仔细看代码,发现我们slice中的数据从来没有释放,所以造成了上面的问题。
如何解决这个问题呢?其实比较简单。只需要将slice,修改成带cache的chan(作为一个队列来使用),当数据使用过后即可销毁。不仅不会再出现内存泄漏,也保证了功能上的一致性。(当然需要重新起一个协程, 由于上面的for 是阻塞的,不会断开,所以也导致了下面的slice 不工作)
http://www.360doc.com/showweb/0/0/936552813.aspx