Go buffered channel容量满时写入未阻塞,关闭日志先输出原因分析
Go通道调度现象问题解答
相关代码
func closeChannel(stream chan int) { fmt.Println("closing the channel") close(stream) } func main() { chanOwner := func() <-chan int { resultStream := make(chan int, 5) go func() { defer closeChannel(resultStream) for i := 0; i <= 5; i++ { resultStream <- i } }() return resultStream } resultStream := chanOwner() for result := range resultStream { // 此处会阻塞直到通道关闭 fmt.Printf("Received: %d\n", result) } fmt.Println("done receiving") }
对应输出
closing the channel Received: 0 Received: 1 Received: 2 Received: 3 Received: 4 Received: 5 done receiving
原因说明
你对缓冲通道的写入逻辑认知是正确的:容量为5的缓冲通道最多支持无阻塞写入5个元素,写入第6个元素i=5时,发送方goroutine确实会在resultStream <- i处阻塞,等待接收方读取释放空间。出现描述中的输出顺序,是Go goroutine调度逻辑和通道唤醒规则共同作用的结果:
- 当main goroutine启动
for range遍历准备接收元素时,阻塞的发送方goroutine会被立刻唤醒:此时有活跃接收方等待,第6个元素不需要写入缓冲,直接拷贝到接收方就完成了发送,发送方的for循环正常结束。 - 发送方goroutine此时仍持有调度权,会优先执行defer注册的
closeChannel函数,先打印closing the channel,再完成通道关闭操作。 - 等到发送方goroutine执行完毕让出调度权后,main goroutine才会继续执行,将已经接收到的6个元素逐个打印,因此出现closing日志早于所有Received日志的现象。
需要注意:这个输出顺序不是固定结果,属于调度实现的正常差异。如果调度器在发送方执行defer逻辑前就切回main goroutine,会出现先打印部分Received日志、再打印closing的情况。
内容的提问来源于stack exchange,提问作者madcolonel10
相关产品推荐
相关产品推荐

