这是一个很经典的面试题,例如现在面试官让你写一个超时控制函数,你是否有注意到内存泄露?
package main
import (
"context"
"fmt"
"time"
)
func job() error {
ctx, _ := context.WithTimeout(context.Background(), time.Second)
done := make(chan struct{})
go func() {
//耗时操作
time.Sleep(time.Millisecond * 1200)
done <- struct{}{}
}()
select {
case <-ctx.Done():
return fmt.Errorf("time out!")
case <-done:
return nil
}
}
func main() {
fmt.Println(job())
}
如果你的代码是这样写的,那么恭喜你,内存泄露了!
我们修改一下 main 函数:
func main() {
for i := 0; i <= 50; i++ {
go func() {
job()
}()
}
for {
time.Sleep(time.Second * 2)
fmt.Println(runtime.NumGoroutine())
}
}
执行一下再看看,效果很神奇。

它会一直打印 52,证明我们上面开的 50 个协程一直没有被释放。

问题其实就出在我画的圈里面,因为每一个程序都超时了,那么就租到了 ←ctx.Done()
这个 case 中去,Job 就算是完成了,但是我们的 ←done
就无法取值了, done ← strcut{}{}
就会被阻塞在这里了。这个协程就永远结束不了。
解决办法就是给 done 一个长度:
done := make(chan struct{}, 1)

只要协程能够往下走,job 中的协程就会被 gc。