英文:
Bug detect, go channels with select
问题
这段代码中应该有一个 bug。我的朋友告诉我,在 select 语句中的超时情况发生之前,go 函数还没有执行完毕,这会导致内存泄漏。他还告诉我,将大小为一的缓冲区添加到 ch 中可以解决这个问题。但我很难理解为什么这样做可以解决问题,如果有人能为我解释一下,我会很感激。我已经尝试过自己搜索答案,但没有成功。
谢谢。
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 timeout\n", 504}
}
}
英文:
There is supposed to be a bug in this bit of code. My mate told me that it contains a memory leak and it occurs when the time out case happens in the select statement before the go function has finished and he also told me that adding a buffer of size one to ch would solve the problem. But i have a hard time understanding why it would solve the problem and would appreciate if someone could explain if for me? I've tried to search for the answer myself but with no success.
Thanks.
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 timeout\n", 504}
}
}
答案1
得分: 2
事实是,没有缓冲区的通道被称为同步通道,它会阻塞发送方和接收方,直到它们完成交换。
就像你必须把东西交给你的伙伴一样,你们都知道见面地点,但不知道时间。先到达的人会等待另一个人,无论是发送方还是接收方。现在,考虑到计算机是愚蠢的 如果其中一个人忘记了约定,另一个人将永远等待。
这里的具体问题是,当选择time.After
(即超时发生)时,再也没有人可以从<-ch
接收了。永远没有。所以可怜的go func()
将永远坐在那里,等待有人接收她的*Response
,但是永远没有人出现。
这实际上不会浪费任何CPU功率,但会浪费内存:用于跟踪通道、goroutine、她的堆栈(无论多小)和她的局部变量所需的内存。直到整个进程终止或被终止,内存将永远不会被回收。
在一个为许多客户端提供服务的服务器中,这将迅速累积,直到应用程序耗尽服务器的所有内存,并且如果你幸运的话,会被操作系统的安全措施杀死,而不会导致整个机器崩溃。
使用带缓冲区的通道是解决此问题的一种方法,因为这样,只要可怜的go func()
准备好她的*Response
,她就可以将其存储到通道的缓冲区中,即使没有人在那里获取它,并平静地终止。一旦发生这种情况,Go的垃圾收集器将注意到没有活动的goroutine持有对该通道的任何指针,因此它将收集通道和它指向的*Response
,并回收所有这些字节。
英文:
The fact is that a channel without a buffer, called synchronous, will block both the sender and the receiver until they can complete their exchange.
Much like if you had to hand over something to your mate and you both knew the meeting place, but not the time. The first one to get there will wait for the other, whether it's the sender or the receiver. Now, given that computers are stupid if one of them forgets about the appointment, the other will verily wait forever.
The specific bug here is that when the select chooses the time.After
(that is, the timeout occurs) nobody will be there to receive from <-ch
anymore. Not ever. So the poor go func()
will sit there forever, waiting for someone to take her *Response
, but nobody will ever show up.
This does not actually waste any CPU power, but it wastes memory: that needed to keep track of the channel, of the goroutine, of her stack—however small—and her local variables. The memory will never be reclaimed until the entire process terminates or is killed.
In a server, serving a lot of clients, this will build up quickly until the application eats all the memory of the server and—if you're lucky—gets killed by the OS safety measures, without taking down the entire machine.
Using a buffered channel is one way to solve the issue, because then, whenever the poor go func()
has her *Response
ready, she will be able to store it into the channel's buffer, even if nobody is there to get it, and terminate peacefully. As soon as that happens, Go's garbage collector will notice that no live goroutine is holding any pointers to that channel anymore, so it will collect the channel and the *Response
it points to and recycle all those bytes.
通过集体智慧和协作来改善编程学习和解决问题的方式。致力于成为全球开发者共同参与的知识库,让每个人都能够通过互相帮助和分享经验来进步。
评论