Go 超时控制导致内存泄露

Golang 207 字

这是一个很经典的面试题,例如现在面试官让你写一个超时控制函数,你是否有注意到内存泄露?

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())
    }

}

执行一下再看看,效果很神奇。

2023-04-10T21:31:47.png

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

2023-04-10T21:31:57.png

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

解决办法就是给 done 一个长度:

done := make(chan struct{}, 1)

2023-04-10T21:32:11.png

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

maksim
Maksim(一笑,吡罗),PHPer,Goper
OωO
开启隐私评论,您的评论仅作者和评论双方可见