Go 语言里的 goroutine 泄露(goroutine leak),它通常发生在:
goroutine 已经没有实际工作可做、也无法退出,但仍然被 Go runtime 保留。
常见情况有这几种:
1. 永久阻塞在 channel
func foo() {
ch := make(chan int)
go func() {
x := <-ch // 一直等,但没人发送
fmt.Println(x)
}()
}
如果后续没人向 ch 发送数据,这个 goroutine 就会一直阻塞。
2. channel 没有消费者
ch := make(chan int)
go func() {
ch <- 1 // 一直卡在这里
}()
如果没有其他 goroutine 接收:
<-ch
这个 goroutine 就无法继续执行,也无法退出。
3. 忘记关闭 channel / 退出条件
典型例子:
func worker(ch <-chan int) {
go func() {
for {
x := <-ch
fmt.Println(x)
}
}()
}
如果 ch 永远不关闭,且没有其他退出机制,这个 goroutine 可能永久运行。
更常见的写法是:
for x := range ch {
fmt.Println(x)
}
然后由生产者:
close(ch)
让消费者自然退出。
4. select 一直等不到退出信号
go func() {
for {
select {
case x := <-ch:
fmt.Println(x)
}
}
}()
如果 ch 永远没有数据,这个 goroutine 会一直活着。
通常应该加一个退出机制:
go func() {
for {
select {
case x := <-ch:
fmt.Println(x)
case <-ctx.Done():
return
}
}
}()
5. HTTP / RPC 请求导致 goroutine 卡住
例如:
go func() {
resp, _ := http.Get(url)
// ...
}()
如果底层网络操作长时间不返回,而且没有合理的 timeout/context,大量请求可能积累成 goroutine 泄露。
更安全的是使用带 timeout 的 context:
ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
defer cancel()
req, _ := http.NewRequestWithContext(ctx, "GET", url, nil)
判断 goroutine leak 的核心
可以记住一句话:
goroutine leak ≠ goroutine 数量多。
真正的问题是:
goroutine 数量不断增长,并且其中一些 goroutine 已经失去了退出路径。
比如:
启动 goroutine
↓
等待 channel / mutex / 网络 / WaitGroup ...
↓
正常情况下应该退出
↓
但退出条件永远无法满足
↓
goroutine 永久存在
↓
反复发生 → goroutine 数量持续增长
如果你是在面试里问 “Go 中什么时候会发生 goroutine 泄露”,最重要的几个关键词就是:
channel 阻塞、缺少退出信号、context 没有传递、网络请求无 timeout、WaitGroup 使用错误。