Go协程执行行为疑问:调度顺序与GOMAXPROCS设置困惑
Go Goroutine 相关疑问解答
我正在阅读《Go in Action》一书,对其中goroutine(协程)部分存在困惑,针对以下代码及输出有两个技术疑问:
package main import ( "fmt" "runtime" "sync" "time" ) var counter int = 0 var wg sync.WaitGroup var mtx sync.Mutex func main() { wg.Add(2) runtime.GOMAXPROCS(1) go incCounter("A") go incCounter("B") wg.Wait() fmt.Println(counter) } func incCounter(prefix string) { fmt.Println("Started thread ", prefix) defer wg.Done() mtx.Lock() { fmt.Println("Incrementing counter from ", prefix) counter = counter + 1 fmt.Println("Passing to another thread") runtime.Gosched() for i := 1; i < 100; i++ { time.Sleep(1 * time.Second) fmt.Println("Thread still executing ", prefix) } } mtx.Unlock() }
输出如下:
Started thread B Incrementing counter from B Passing to another thread Started thread A Thread still executing B Thread still executing B Thread still executing B Thread still executing B
疑问:
- 代码中先启动goroutine A,为何实际先执行goroutine B?
- 已将
runtime.GOMAXPROCS设置为1,goroutine B调用runtime.Gosched()后,因mtx处于锁定状态,goroutine A需等待解锁,但输出看似两个goroutine在并行执行,这是否符合预期?
解答:
问题1:先启动A却先执行B的原因
Go的goroutine调度器不保证goroutine的启动顺序和执行顺序一致。调用go incCounter("A")只是将该goroutine加入待执行队列,此时main goroutine会继续执行go incCounter("B")。调度器会根据自身策略(如当前goroutine状态、队列优先级等)选择下一个执行的goroutine,因此先启动的A不一定先被调度,B先执行是完全正常的,这是goroutine调度不确定性的体现。
问题2:GOMAXPROCS=1下的执行是否符合预期
这完全符合预期,并非真正的并行执行:
- 当goroutine B调用
runtime.Gosched()时,会主动让出当前CPU时间片,调度器切换到goroutine A执行。 - 但goroutine A执行到
mtx.Lock()时,发现锁已被B持有,会进入阻塞状态,此时调度器会重新切回goroutine B继续执行(B只是主动让出时间片,并未被阻塞,仍处于可执行状态)。 - 输出里的
Started thread A是A被调度后执行的第一行打印,之后因锁阻塞,调度器切回B执行循环打印,看起来像“并行”,但实际是在单个CPU核心上的并发调度切换,并非真正意义上的并行。
内容的提问来源于stack exchange,提问作者Pedro Sturmer
相关产品推荐
相关产品推荐

