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

Go语言向已关闭缓冲channel写入为何结果随机?

向已关闭的缓冲Channel发送数据导致随机结果的原因分析

问题代码

func writeToClosedBufferedChannel() {
    ch := make(chan int, 2)
    ch <- 10
    ch <- 20

    go func() {
        fmt.Println("test")
        ch <- 30
    }()

    time.Sleep(100 * time.Millisecond)
    fmt.Println("before")
    close(ch)

    fmt.Println("after")
    for value := range ch {
        fmt.Println(value)
    }
}

运行的三种随机结果

test
before
after
panic: send on closed channel

test
before
after
10
20

test
before
after
10
20
panic: send on closed channel

原因分析

三种随机结果的核心是goroutine调度的不确定性,以及Channel关闭和发送操作的竞态关系:

  1. 第一种结果:发送阻塞后遇Channel关闭触发panic
    主goroutine执行time.Sleep(100ms)后,子goroutine已执行到ch <- 30,但此时Channel缓冲已满(容量2,已存10、20),子goroutine被阻塞在发送操作上,进入Channel的sendq等待队列。随后主goroutine关闭Channel,处于sendq中的发送操作会立即触发panic,主goroutine的range循环还未执行就被中断。

  2. 第二种结果:发送操作未被调度,程序正常退出
    主goroutine关闭Channel后,子goroutine的ch <- 30因调度延迟还未执行。等子goroutine获得调度机会时,主goroutine已经通过range循环读完缓冲里的10、20并退出程序,发送操作没机会执行,因此不会触发panic。

  3. 第三种结果:range循环完成后发送触发panic
    主goroutine关闭Channel后,先执行range循环读完10、20,此时子goroutine才被调度执行ch <- 30,因Channel已关闭,直接触发panic。

关于sendq的细节

当缓冲Channel已满时,发送操作会将当前goroutine放入Channel的sendq队列,等待接收端读取数据腾出空间。若此时Channel被关闭,Go运行时会遍历sendq中的所有goroutine,唤醒它们并让发送操作触发panic。但这个唤醒动作和主goroutine的range循环执行顺序完全由Go调度器决定,没有固定规则,这就是结果随机的根本原因。

内容的提问来源于stack exchange,提问作者QuickDzen

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.02 09:57:39