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

GOMAXPROCS=1时Go通道发送为何不按协程创建顺序输出?

单P(GOMAXPROCS=1)场景下Go协程调度顺序问题解答

问题描述

设置runtime.GOMAXPROCS(1)后编写Go代码:创建10个foo协程尝试向无缓冲通道c发送数据,之后创建10个接收协程。但输出结果中第一个发送消息的是最后创建的协程9,而非按0到9的顺序输出。已知P>1时多线程调度会导致无序,但疑惑单P场景下为何出现此情况,附上代码及输出:

代码

import (
    "fmt"
    "runtime"
    "time"
)

func main() {
    runtime.GOMAXPROCS(1)
    c := make(chan struct{})
    ok := make(chan int)
    n := 10
    for i := 0; i < n; i++ {
        go foo(i, c, ok)
    }
    time.Sleep(time.Millisecond)
    for i := 0; i < n; i++ {
        go func() {
            <-c
        }()
    }
    time.Sleep(time.Second)
    close(ok)
}

func foo(i int, c chan struct{}, ok chan int) {
    for {
        select {
        case c <- struct{}{}:
            fmt.Printf("%d sendMsg!\n", i)
        case <-ok:
            fmt.Printf("%d closed\n", i)
            return
        }
    }
}

输出

9 sendMsg!
0 sendMsg!
1 sendMsg!
2 sendMsg!
3 sendMsg!
4 sendMsg!
5 sendMsg!
6 sendMsg!
7 sendMsg!
8 sendMsg!

原因分析

即使设置GOMAXPROCS(1),Go协程调度器也不保证协程执行顺序与创建顺序一致,核心原因如下:

  • 协程创建后的调度时机:循环中创建协程时,主协程不会立刻让出CPU,而是先完成整个循环(创建完10个协程),直到进入time.Sleep时,调度器才开始调度等待的协程。
  • 单P下的协程队列调度逻辑:单P场景下,调度器维护的可运行协程队列并非严格遵循先进先出(FIFO)。最后创建的协程9被加入队列末尾,但调度器选取下一个执行协程时,可能从队列尾部优先选择,导致后创建的协程先被调度。
  • 无缓冲通道的唤醒策略:所有foo协程启动后,因无接收方会阻塞在通道发送操作上,进入等待队列。当第一个接收协程创建后,通道会唤醒等待发送的协程,而唤醒的选择顺序不遵循协程创建顺序,刚好协程9被第一个选中执行。

简言之,Go协程调度器的设计目标是高效公平调度,本身就不保证协程执行顺序与创建顺序一致,单P场景下同样如此。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.21 03:32:37