go协程泄露 goroutine leak

goroutine leak

什么是泄露?

1
不好的事情流传出去让别人知道了

额……,这个解释对程序员来说好像不太贴近生活,那我们换一个

内存泄露(Memory Leak)

1
2
3
4
5
6
7
8
对于java程序员来说,这个词汇肯定不陌生.
内存泄露本意是申请的内存空间没有被正确释放,
导致后续程序里这块内存被永远占用(不可达),
而且指向这块内存空间的指针不再存在时,
这块内存也就永远不可达了,内存空间就这么一点点被蚕食.
借用别人的比喻就是:
比如有10张纸,本来一人一张,画完自己擦了还回去,别人可以继续画,现在有个坏蛋要了纸不擦不还,然后还跑了找不到人了,如此就只剩下9张纸给别人用了,这样的人多起来后,最后大家一张纸都没有了;

go协程泄露(goroutine leak)

1
2
在Go中,goroutine很轻量级,随便创建成千上万个goroutine不是问题,  
但要注意,要是这么多的goroutine一致递增,而不退出,不释放资源,也会造成资源耗尽、服务不可达的情况;

如何发现

1
2
// NumGoroutine returns the number of goroutines that currently exist.
runtime.NumGoroutine()

我们可以把这个数据封装成一个服务,通过查看每次的协程数量的变化和增减,我们可以判断是否有goroutine泄露发生

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
package main

import (
"fmt"
"log"
"net/http"
"runtime"
"strconv"
)

//func getStackTraceHandler(w http.ResponseWriter, r *http.Request) {
// stack := debug.Stack()
// w.Write(stack)
// pprof.Lookup("goroutine").WriteTo(w, 2)
//}

func getGCntHandler(w http.ResponseWriter, r *http.Request) {
cnt := runtime.NumGoroutine()
fmt.Fprintf(w, strconv.Itoa(cnt))
}
func main() {
//http.HandleFunc("/_stack", getStackTraceHandler)
http.HandleFunc("/_gc", getGCntHandler)
err := http.ListenAndServe(":8080", nil)
if err != nil {
log.Fatal("ListenAndServe: ", err)
}
}


如何确认泄露处

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32

package main

import (
"fmt"
"log"
"net/http"
"runtime"
"runtime/debug"
"runtime/pprof"
"strconv"
)

func getStackTraceHandler(w http.ResponseWriter, r *http.Request) {
stack := debug.Stack()
w.Write(stack)
pprof.Lookup("goroutine").WriteTo(w, 2)
}

func getGCntHandler(w http.ResponseWriter, r *http.Request) {
cnt := runtime.NumGoroutine()
fmt.Fprintf(w, strconv.Itoa(cnt))
}
func main() {
http.HandleFunc("/_stack", getStackTraceHandler)
http.HandleFunc("/_gc", getGCntHandler)
err := http.ListenAndServe(":8080", nil)
if err != nil {
log.Fatal("ListenAndServe: ", err)
}
}

常见的产生原因

  • 由于channel的读/写端退出,导致channel的写/读端goroutine一直阻塞,而无法退出
  • goroutine内部进入死循环,导致资源一直无法释放

解决方案

基于以上原因,开发时需要重点注意以下几点:

  • 创建goroutine时就要想好该goroutine该如何结束.
    比如:
    • goroutine完成它的工作,正常return
    • 由于发生了没有处理的错误,运行时异常
    • 有其他的协程告诉它终止
  • 使用channel时,要考虑到channel阻塞时协程可能的行为
  • 在做 master-worker模式、producer-consumer模式等的开发时