在一次面试中被问到这个问题,当时不是特别了解。后续学习协程池时候也遇到了这个概念(协程池的优势),因此特来学习一下。
Go的并发是以goroutine和channel的形式实现的。协程泄露是指goroutine创建后,由于一些错误,长时间得不到释放。继续不断创建新的goroutine协程,最终导致内存耗尽,程序崩溃。
简单来说,协程泄露就是指创建的goroutine炸了导致无法释放,所以一直卡的占内存,长期以往让整个系统直接挂掉。
chan
相关1、发送不接收
2、接收不发送
3、没有初始化
chan
本身的一些操作,会导致阻塞。
比如对于一个无缓存通道的发送操作,必须等到有地方接收,才会正常进行下去,否则就会一直阻塞。接收必须等到有发送,否则也会阻塞。
同理,如果对于有缓存通道已满的情况的发送,或者为空情况的接收也是如此。
因此如果接收不发送
或者发送不接收
,都会带来阻塞的问题。
其次,如果chan
忘记初始化,这里的chan
则是nil
,所以也会一直阻塞。(相关测试见此文)
1、I/O操作的阻塞
2、逻辑上的死循环
对于逻辑死循环,好理解,就是单纯写错代码死循环了,所以导致此goroutine无法结束。
对于I/O操作阻塞,常见的是如发送HTTP请求至服务器没有使用超时。
直接引用煎鱼这篇文章中的测试用例。
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())
}
}
这种在协程中去请求,因为网络等原因造成超时响应,自然会一直卡住。
因此需要记得手动设置一下超时时间。
httpClient := http.Client{
Timeout: time.Second * 15,
}
1、互斥锁没解锁
2、同步锁用错了
实际情况来看,第一个就是单纯加了锁忘记解,第二种就是用完wg.add(1)
,忘记了wg.done
,导致持续wait()
。因此解决方法是加了记得解,go中使用defer是多么方便的一件事情…
var mutex sync.Mutex
for i := 0; i < 10; i++ {
go func() {
mutex.Lock()
defer mutex.Unlock()
total += 1
}()
}
var wg sync.WaitGroup
for i := 0; i < v; i++ {
wg.Add(1)
defer wg.Done()
fmt.Println("脑子进煎鱼了")
}
wg.Wait()
select
会在所有case
中随机选择一个满足的去执行,如果所有均不满足,则会执行default
中的命令。如果没有加default
的话,则会持续阻塞,直到任意一个满足再去执行。
因此如果没有设置default
,且一直无法满足,则会长时间阻塞。
网络上普遍使用 pprof
进行检测。因为目前没有实战遇到,所以仅仅是理论学习为主,埋下坑了解为先。
相关资源:
https://www.cnblogs.com/xiaowangba/p/6313755.html
https://cloud.tencent.com/developer/article/2291519
Goroutine泄露是Golang中常见的一种错误,网上有话说“Go 10次内存泄漏,8次goroutine泄漏,1次是真正内存泄漏,还有1次是cgo导致的内存泄漏”。因此一定不可掉以轻心。