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

为何Go语言中去掉time.Sleep后fmt.Println执行顺序不符合预期?

Go无缓冲Channel下Goroutine调度与输出顺序异常解析

问题描述

我在调试一段Go代码时发现,无缓冲Channel的goroutine执行顺序和输出结果与预期不符,只有添加time.Sleep时才能得到预期的交替输出,去掉或缩短休眠时长后结果混乱。

代码示例

func main() {
    var c chan int = make(chan int)
    go sendingThrowingResults(c)

    for val := range c {
        fmt.Println(val)
    }

    fmt.Println("finished all")
}

func sendingThrowingResults(c chan int) {
    var results []int = []int{1, 2, 3, 6, 4, 8}

    for _, val := range results {
        // time.Sleep(100 * time.Millisecond)
        c <- val
        fmt.Println("finished")
    }
    defer close(c)
}

预期输出

finished
1
finished
2
finished
3
finished
6
finished
4
finished
8
finished all

实际异常输出

finished
1
2
finished
finished
3
finished
6
4
finished
finished
8
finished all

原因解析

核心问题在于Go Goroutine的调度时机是由Runtime自主决定的,无缓冲Channel仅保证发送与接收的同步,不保证同步完成后两个Goroutine的执行顺序:

  • 无缓冲Channel的同步特性:c <- val会阻塞,直到主Goroutine完成接收。但接收完成后,子Goroutine(执行sendingThrowingResults)和主Goroutine都进入可运行状态,调度器可以选择优先执行任意一个。
  • 调度器的不确定性:
    当没有time.Sleep时,主Goroutine在接收值后,可能因为时间片未耗尽、调度策略倾向等原因,继续执行fmt.Println(val)并回到循环接收操作,而子Goroutine的fmt.Println("finished")被延迟执行。
    子Goroutine完成fmt.Println("finished")后,立刻发送下一个值,主Goroutine再次接收并打印,就会出现连续的数字输出;随后子Goroutine才批量执行之前延迟的fmt.Println("finished"),导致连续的"finished"输出。
  • time.Sleep的作用:time.Sleep会主动让子Goroutine让出CPU,强制触发调度,让主Goroutine有足够时间完成打印并回到阻塞状态,从而保证严格的交替执行顺序。

验证与解决

如果需要保证严格的交替输出,可以在主Goroutine的打印操作后主动让出CPU,引导调度器切换到子Goroutine:

import "runtime"

func main() {
    var c chan int = make(chan int)
    go sendingThrowingResults(c)

    for val := range c {
        fmt.Println(val)
        runtime.Gosched() // 主动让出CPU,触发调度
    }

    fmt.Println("finished all")
}

这样修改后,输出会与预期一致。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.20 06:37:06