该Goroutine是否会永久阻塞?缓冲通道是否为可行解决方案?
问题解答:Go协程阻塞与缓冲通道的作用
直接给你结论:当select选中context取消分支后,那个向无缓冲doneChan发送数据的goroutine会永久阻塞,进而导致goroutine泄漏;使用缓冲通道确实可以解决这个问题。
咱们一步步拆解分析:
1. 无缓冲通道为何会导致永久阻塞
先看你的原代码:
func test() error { ctx, cancel := context.WithCancel(context.Background()) cancel() doneChan := make(chan bool) // 无缓冲通道 go func() { // 模拟长时间运行的任务 time.Sleep(time.Minute) doneChan <- true // 这里会永久阻塞吗? }() select { case <- ctx.Done(): return ctx.Err() case <- doneChan: return nil } }
当cancel()被调用后,ctx.Done()会立刻触发,select会优先选中这个分支,test函数随即返回。此时那个子goroutine还在执行time.Sleep,等它醒来执行doneChan <- true时,已经没有任何goroutine在等待接收这个通道的数据了——因为test函数已经退出,原本的select接收逻辑早已不存在。
而无缓冲通道的发送操作必须要有对应的接收操作才能完成,所以这个子goroutine会一直卡在doneChan <- true这一行,永远无法退出,造成goroutine泄漏。
2. 缓冲通道的解决方案
如果把doneChan改成带1个缓冲容量的通道,问题就迎刃而解了:
doneChan := make(chan bool, 1) // 缓冲通道,容量为1
当子goroutine执行doneChan <- true时,因为通道有一个缓冲空位,发送操作会直接完成,不需要等待接收方。这个goroutine发送完成后就会正常退出,不会出现阻塞和泄漏的情况。
简单来说,缓冲通道允许发送操作在没有即时接收方的情况下完成,只要通道的缓冲还有剩余空间。这里设置容量1刚好满足一次发送的需求,完美适配这个场景的问题。
内容的提问来源于stack exchange,提问作者user2494770
相关产品推荐
相关产品推荐

