错误检测,选择频道



这段代码中应该有一个bug。我的伙伴告诉我,它包含内存泄漏,当在go函数完成之前select语句中发生超时情况时,就会发生这种情况。他还告诉我,在ch中添加一个大小为1的缓冲区可以解决这个问题。但我很难理解为什么它能解决问题,如果有人能为我解释一下,我会很感激?我试着自己寻找答案,但没有成功。

谢谢。

func Read(url string, timeout time.Duration) (res *Response) {
    ch := make(chan *Response)
    go func() {
        time.Sleep(time.Millisecond * 300)
        ch <- Get(url)
    }()
    select {
    case res = <-ch:
    case <-time.After(timeout):
        res = &Response{"Gateway timeoutn", 504}
    }
}

事实是,一个没有缓冲区的通道,称为同步通道,将阻塞发送方和接收方,直到它们能够完成交换。

就像你必须把东西交给你的伴侣,而你们都知道见面的地点,但不知道时间。第一个到达那里的人将等待另一个,无论是发送者还是接收者。现在,考虑到计算机是愚蠢的:-)如果其中一个忘记了约会,另一个肯定会永远等待。

这里的特定错误是,当select选择time.After(即发生超时)时,将不再有人从<-ch接收。从来没有。所以可怜的go func()将永远坐在那里,等待有人拿走她的*Response,但没有人会出现。

这实际上并没有浪费任何CPU功率,但它浪费了内存:跟踪通道、goroutine、堆栈(无论多么小)和局部变量所需的内存。在整个进程终止或终止之前,内存永远不会被回收。

在为许多客户端提供服务的服务器中,这种情况会迅速累积,直到应用程序吃掉服务器的所有内存,如果幸运的话,它会被操作系统的安全措施杀死,而不会导致整个机器瘫痪。

使用缓冲通道是解决这个问题的一种方法,因为这样,每当较差的go func()准备好她的*Response时,她就可以将其存储到通道的缓冲区中,即使没有人可以获得它,也可以和平终止。一旦发生这种情况,Go的垃圾收集器就会注意到,没有任何活动goroutine再持有指向该通道的指针,因此它将收集通道及其指向的*Response,并回收所有这些字节。

最新更新