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

使用通道时Goroutine的执行顺序问题及代码异常分析

Goroutine通道操作的执行顺序与阻塞问题解析

问题描述

使用通道时Goroutine的执行顺序是怎样的?原本认为对通道进行读写操作会阻塞当前Goroutine,但测试代码却不符合这一规则:

func main() {
    ch := make(chan int)
    go sum(ch, 3)
    fmt.Println("Write number: 10")
    ch <- 10
    fmt.Println("Write number: 20")
    ch <- 20
    fmt.Println("Write number: 30")
    ch <- 30

    fmt.Println("Finish main")
}

func sum(ch chan int, len int) {
    fmt.Println("Func 'sum' start")

    sum := 0
    for i := 0; i < len; i++ {
       fmt.Println("For start")
       num := <-ch
       fmt.Printf("Read from ch: %d\n", num)
       sum += num
       fmt.Println("For finish")
    }

    fmt.Printf("Sum: %d\n", sum)
}

预期执行流程是主Goroutine每次向通道写入数据后会阻塞,直到sum Goroutine读取数据后才继续执行,输出为交替的写入提示与读取操作日志。但实际输出中主Goroutine连续写入了20和30才被读取。此外,当将sum的第二个参数改为2时,主Goroutine写入第三个值后无读取者,却未触发报错,这是为什么?


问题解答

1. 为什么主Goroutine会出现“连续写入”的现象?

你使用的是无缓冲通道,理论上每次发送操作ch <- x都会阻塞当前Goroutine,直到有其他Goroutine完成接收操作<-ch。实际出现“连续写入”的视觉效果,主要有两个原因:

  • Go调度器的时机不确定性:主Goroutine在被唤醒后,会快速执行后续的打印语句(比如fmt.Println("Write number:20")),之后才进入下一次发送阻塞。而sum Goroutine的调度唤醒可能存在延迟,导致它的打印输出和主Goroutine的打印输出交错,看起来像是主Goroutine连续完成了写入。
  • 标准输出的缓冲特性:Go的fmt.Println输出是带缓冲的,多个打印语句的内容可能被暂时缓冲,之后一次性输出,这也会造成“连续写入”的错觉。

本质上无缓冲通道的阻塞规则没有被打破,只是执行流程的可视化输出因为调度和缓冲出现了偏差。

2. 写入无接收者的通道为何未触发报错?

当sum的参数改为2时,sum Goroutine只执行2次接收操作就会退出,此时通道不再有活跃的接收者。但主Goroutine写入第三个值后没有触发panic,是因为主Goroutine会在写入后立刻执行完剩余代码并退出——Go程序的生命周期由主Goroutine主导,主Goroutine退出时,所有其他Goroutine会被强制终止,程序直接结束,主Goroutine还没来得及因为“发送到无接收者的通道”而触发panic。

如果想验证这个panic,可以在主Goroutine写入第三个值后添加等待逻辑,比如:

ch <- 30
time.Sleep(time.Second) // 让主Goroutine等待足够时间
fmt.Println("Finish main")

此时主Goroutine会在发送操作上持续阻塞,因为没有接收者,最终会触发panic。


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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.10 01:36:07