首页 > 文章列表 > Go语言协程:为什么协程的执行顺序并非我预想的那样?

Go语言协程:为什么协程的执行顺序并非我预想的那样?

402 2025-03-16

Go语言协程:为什么协程的执行顺序并非我预想的那样?

深入Go语言协程调度机制:揭秘执行顺序的奥秘

本文将深入探讨Go语言协程的执行顺序问题,澄清一个常见的误解:Go协程的执行并非严格按照启动顺序或先进先出队列进行。 以下示例代码阐述了这一关键点:

package main

import (
    "fmt"
    "runtime"
    "sync"
)

func main() {
    runtime.GOMAXPROCS(1)
    wg := sync.WaitGroup{}
    wg.Add(10)
    for i := 0; i < 10; i++ {
        if i < 5 {
            go func() {
                defer wg.Done()
                fmt.Println("a:", i)
            }()
        } else {
            go func(num int) {
                defer wg.Done()
                fmt.Println("b:", num)
            }(i - 5)
        }
    }
    wg.Wait()
}

这段代码启动了十个协程,前五个使用闭包变量i,后五个使用参数num传递数值。 直觉上,输出顺序应为先打印五个"a:",再打印五个"b:",或至少按照启动顺序依次打印。然而,实际运行结果可能出现"b: 4"先打印的情况,这与预期不符。

造成这种现象的原因在于Go语言协程调度器的非确定性。Go 1.5版本之后,协程调度机制发生变化,官方明确指出:不要依赖于goroutine的调度顺序,这是未定义的行为。

即使设置了runtime.GOMAXPROCS(1)限制为单核运行,也无法保证协程执行顺序的确定性。调度器会根据自身策略选择下一个运行的协程,该策略并非简单的FIFO队列。

因此,闭包变量i的值在协程启动后才最终确定,导致前五个协程都打印了i的最终值5。而后五个协程则正确打印了0到4。"b: 4"能够先执行,是由于调度器的选择,而非任何可预测的顺序。

结论:编写Go代码时,切勿依赖协程的执行顺序。任何依赖未定义行为的代码都是不可靠的。 应使用同步机制(如WaitGroup、通道等)协调协程执行,确保程序按照预期逻辑运行,而非依赖不可预测的调度顺序。

来源:1741317667