2.13 有哪些情况会导致协程泄露? =============================== 协程泄露是指,在程序运行过程中,有一些协程由于某些原因,无法正常退出。 协程的运行是需要占用内存和 CPU 时间的,一旦这种协程越来越多,会导致内存无端被浪费,CPU 时间片被占用,程序会越来越卡。 那会导致协程泄露的原因有哪些呢? 其实不用死记硬背,只要有可能导致程序阻塞的,都有可能会导致协程泄露。 那么会让程序阻塞的,无外乎: - 通道阻塞 - 锁阻塞 - 等待阻塞 1. 通道使用不当 --------------- 通道和协程是 Go 的两大杀器,配合好了是非常香的,但要是没用好,就会造成协程泄露。 下面是常见的一些通道使用不当导致的协程泄露例子 **发送了却没全接收** .. code:: go func main() { for i := 0; i < 4; i++ { queryAll() fmt.Printf("goroutines: %d\n", runtime.NumGoroutine()) } } func queryAll() int { ch := make(chan int) for i := 0; i < 3; i++ { go func() { ch <- query() }() } return <-ch } func query() int { n := rand.Intn(100) time.Sleep(time.Duration(n) * time.Millisecond) return n } **没发送却有人在接收** .. code:: go func main() { defer func() { fmt.Println("goroutines: ", runtime.NumGoroutine()) }() var ch chan struct{} go func() { ch <- struct{}{} }() time.Sleep(time.Second) } **初始化通道却没分配内存** .. code:: go func main() { defer func() { fmt.Println("goroutines: ", runtime.NumGoroutine()) }() var ch chan int go func() { <-ch }() time.Sleep(time.Second) } 2. 锁使用不当 ------------- **加互斥锁后没有解锁** 加了互斥锁后,若没有释放,其他 Goroutine 再想获取锁就会阻塞。 因此在加了互斥锁后,可以下意识加个 defer mutex.Unlock(),养成编码习惯 .. code:: go func main() { total := 0 defer func() { time.Sleep(time.Second) fmt.Println("total: ", total) fmt.Println("goroutines: ", runtime.NumGoroutine()) }() var mutex sync.Mutex for i := 0; i < 10; i++ { go func() { mutex.Lock() // 正常加锁后,可以下意识加个 defer mutex.Unlock() total += 1 }() } } **同步锁使用不当** 如下例子中,wg.Add 的数量与 wg.Done 的数量不一致,就会导致 wg.Wait 阻塞。 .. code:: go func handle(v int) { var wg sync.WaitGroup wg.Add(5) for i := 0; i < v; i++ { wg.Done() } wg.Wait() } func main() { defer func() { fmt.Println("goroutines: ", runtime.NumGoroutine()) }() go handle(3) time.Sleep(time.Second) } 3. 慢等待未响应 --------------- 在下面这个例子中,发送一个 http 请求的时候,如果网络非常的卡,导致这个请求一直没有收到响应,那么就会一直进行 for 循环,不断创建新的协程。 .. code:: go func main() { for { go func() { _, err := http.Get("https://www.xxx.com/") if err != nil { fmt.Printf("http.Get err: %v\n", err) } // do something... }() time.Sleep(time.Second * 1) fmt.Println("goroutines: ", runtime.NumGoroutine()) } }