You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.09.28 16:06:03