为何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
相关产品推荐
相关产品推荐

