若是你启动了一个 goroutine,但并无符合预期的退出,直到程序结束,此goroutine才退出,这种状况就是 goroutine 泄露。当 goroutine 泄露发生时,该 goroutine 的栈(通常 2k 内存空间起)一直被占用不能释放,goroutine 里的函数在堆上申请的空间也不能被 垃圾回收器 回收。这样,在程序运行期间,内存占用持续升高,可用内存越来也少,最终将致使系统崩溃。html
回顾一下 goroutine 终止的场景:segmentfault
那么当这三者同时没发生的时候,就会致使 goroutine 始终不会终止退出。tcp
goroutine泄露通常是由于channel操做阻塞而致使整个routine一直阻塞等待或者 goroutine 里有死循环的时候。能够细分为下面五种状况:函数
// leak 是一个有 bug 程序。它启动了一个 goroutine 阻塞接收 channel。当 Goroutine 正在等待时,leak 函数会结束返回。此时,程序的其余任何部分都不能经过 channel 发送数据,那个 channel 永远不会关闭,fmt.Println 调用永远不会发生, 那个 goroutine 会被永远锁死 func leak() { ch := make(chan int) go func() { val := <-ch fmt.Println("We received a value:", val) }() }
// 一个复杂一点的例子 func sendMsg(msg, addr string) error { conn, err := net.Dial("tcp", addr) if err != nil { return err } defer conn.Close() _, err = fmt.Fprint(conn, msg) return err } func broadcastMsg(msg string, addrs []string) error { errc := make(chan error) for _, addr := range addrs { go func(addr string) { errc <- sendMsg(msg, addr) fmt.Println("done") }(addr) } for _ = range addrs { if err := <-errc; err != nil { return err } } return nil } func main() { addr := []string{"localhost:8080", "http://google.com"} err := broadcastMsg("hi", addr) time.Sleep(time.Second) if err != nil { fmt.Println(err) return } fmt.Println("everything went fine") }
对于 broadcastMsg 里的这一段工具
for _ = range addrs { if err := <-errc; err != nil { return err } }
当遇到 第一条不为 nil 的 err,broadcastMsg就返回了,那么从第二个调用 sendMsg 后返回值 err 不为 nil 的 goroutine 在 errc <- sendMsg(msg, addr)
这里都将阻塞而形成这些 goroutine 不能退出。ui
和第二种状况比较相似。google
在 channel 的接收值数量有限,且能够用 buffered channel 的状况下,那 buffer size 就分配的和 接收值数量 同样,这样能够解决掉第二、3种缘由形成的泄露。好比在第二种中,改为spa
errc := make(chan error, len(addrs))
问题就解决了。code
注意:time package里的定时器使用不当也会形成 goroutine 泄露。协程
tick := time.Tick(1 * time.Second) for countdown := 10; countdown > 0; countdown-- { fmt.Println(countdown) select { case <-tick: // Do nothing. case <-abort: fmt.Println("aborted!") return } }
以上的代码中,当 for 循环结束后,tick 将再也不有接收者,time.Tick 启动的 goroutine 将产生泄露。
建议在程序的整个生命周期须要 ticks 时才使用 time.Tick,不然建议按以下模式使用:
ticker := time.NewTicker(1 * time.Second) <- ticker.C ticker.Stop() // 当再也不使用后,结束 ticker 的 goroutine
实现一个 fibonacci 数列生成器,并在独立的 goroutine 中运行,在读取完须要长度的数列后,若是 用于 退出生成器的 quit 忘了被 close (或写入数据),select 将一直被阻塞形成 该 goroutine 泄露。
func fibonacci(c, quit chan int) { x, y := 0, 1 for{ select { case c <- x: x, y = y, x+y case <-quit: fmt.Println("quit") return } } } func main() { c := make(chan int) quit := make(chan int) go fibonacci(c, quit) for i := 0; i < 10; i++{ fmt.Println(<- c) } // close(quit) }
在这种须要一个独立的 goroutine 做为生成器的场景下,为了能在外部结束这个 goroutine,咱们一般有两种方法:
func fibonacci(c chan int, ctx context.Context) { x, y := 0, 1 for{ select { case c <- x: x, y = y, x+y case <-ctx.Done(): fmt.Println("quit") return } } } func main() { ctx, cancel := context.WithCancel(context.Background()) defer cancel() c := make(chan int) go fibonacci(c, ctx) for i := 0; i < 10; i++{ fmt.Println(<- c) } cancel() time.Sleep(5 * time.Second) }
一般因为代码里循环的退出条件实现的不对,致使死循环。
// 粗暴的示例 func foo() { for{ fmt.Println("fooo") } }
这篇文章 有关于检测和定位更详细的描述,能够参考,本处再也不累述。
参考:
https://segmentfault.com/a/11...
https://www.ardanlabs.com/blo...