目录
一.怎么避免goroutine泄露问题?
func main() {
//协程泄露的问题
//产生泄露问题的demo如下
//一.goroutine+channel结合
1.发送不接受(接受不完全)
//for i := 0; i < 4; i++ {
// queryAll()
// fmt.Printf("接受的数:%d \n,", runtime.NumGoroutine())
// //最后输出的 goroutines 数量是在不断增加的,每次多 2 个。也就是每调用一次,都会泄露 Goroutine。
// //NumGoroutine 获取当前运行中的 goroutine 数量粗略估计。如果 goroutine 随着时间增加,数量在不断上升,而基本没有下降,基本可以确定存在泄露。
// //这里的泄露在于 queryAll每次都会往channel发送三个数,但接受端并没有接受完全(仅接受了一个ch),所以产生泄露
//}
2.接受不发送
//defer func() {
// fmt.Printf("已存在的goroutines:%d\n", runtime.NumGoroutine())
//}()
//var ch chan struct{}
//func() {
// ch <- struct{}{}
//}()
//time.Sleep(time.Second)
这个就是channel接受了值,但并没有发送,也会产生阻塞
在业务场景中,许多业务逻辑里,基本只要有一个channel读写出现问题都会发生阻塞
3.nil channel
//defer func() {
// fmt.Printf("已存在的goroutines:%d\n", runtime.NumGoroutine())
// 获取当前运行中的 goroutine 数量粗略估计,
//
//}()
//var ch chan int
//func() {
// ch <- 1
//}()
//fmt.Println(<-ch)
//time.Sleep(time.Second)
这个demo在于没有进行初始化channel,所以无论是读或者写这个通道都是不可用的
3.1正确初始化方式如下
//ch := make(chan int)
//go func() {
// ch <- 0
//}()
//go func() {
// ch <- 1
//}()
//fmt.Println(<-ch)
//time.Sleep(time.Second)
二.奇怪的慢等待
//
//for {
// go func() {
// _, err := http.Get("https://www.baidu.com/")
// if err != nil {
// fmt.Printf("http.Get err: %v\n", err)
// }
// // do something...
//
// }()
//
// time.Sleep(time.Second * 1)
// fmt.Println("goroutines: ", runtime.NumGoroutine())
//}
这个奇怪的慢等待demo 展示了Go语言中比肩常见的经典场景,也就是一般应用程序访问第三方接口时,而第三方接口迟迟不返回响应结果,刚好Go语言中的http.Client没有设置超时时间,因此会导致阻塞导致goroutine暴涨,导致不断泄露,最后导致事故。
三.互斥锁忘记解锁
//total := 0
//defer func() {
// time.Sleep(time.Second)
// fmt.Printf("total:%d\n", total)
// fmt.Printf("当前正在运行的goroutine数量:%d\n", runtime.NumGoroutine())
//}()
//var mutex sync.Mutex
//for i := 0; i < 10; i++ {
// func() {
// mutex.Lock()
// defer mutex.Unlock() //在使用sync.Mutex互斥锁时时这里Unlock需要注意
// total++
//
// }()
//}
在这个例子中第一个互斥锁加锁了,但没有进行Unlock,因此导致后面的所有 sync.Mutex 想加锁,却因未释放又都阻塞住了,直接goroutine泄露导致死锁现象,正确应该在mutex.lock后加一个mutex.Unlock
//四.同步锁使用不当
defer func() {
fmt.Println("goroutines: ", runtime.NumGoroutine())
}()
go handle(3)
time.Sleep(time.Second)
}
func queryAll() int {
ch := make(chan int)
for i := 0; i < 3; i++ {
go func() {
ch <- queryOne()
}()
}
return <-ch
}
func queryOne() int {
n := rand.Intn(10) //返回一个半开区间的随机整数,当n<=0,会发生panic
time.Sleep(time.Duration(n) * time.Millisecond) //一个持续时间以int64纳秒计数表示两个瞬间之间的时间。这种表示方法将最大的可表示的持续时间限制在大约290年。
return n
}
func handle(v int) {
错误用例
//var wg sync.WaitGroup
//wg.Add(5)
//for i := 0; i < v; i++ {
// fmt.Println("脑子进煎鱼了")
// wg.Done()
//}
//wg.Wait()
//在这个例子中,我们调用了同步编排 sync.WaitGroup,模拟了一遍我们会从外部传入循环遍历的控制变量。
//但由于 wg.Add 的数量与 wg.Done 数量并不匹配,因此在调用 wg.Wait 方法后一直阻塞等待。
var wg sync.WaitGroup
for i := 0; i < v; i++ {
wg.Add(1)
defer wg.Done()
fmt.Println("脑子进煎鱼了")
}
wg.Wait()
}
二.排查goroutine泄露
主要用`runtime.NumGoroutine`或者`pprof`工具。`pprof`会返回所有带有堆栈跟踪的`goroutine`列表。
三.参考资源:
煎鱼------[跟读者聊 Goroutine 泄露的 N 种方法,真刺激!](https://blog.csdn.net/EDDYCJY/article/details/115535237)