golang调度程序如何在以下代码中调度goroutine?

How does the golang scheduler sched goroutine in following code?

package main

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

var quit chan int = make(chan int)

func loop(a int) {
    fmt.Println(a)
    for i := 0; i < 30000000000; i++ {
    }
    fmt.Println(a)
    quit <- 0
}

func main() {
    runtime.GOMAXPROCS(1)

    go loop(1)
    time.Sleep(time.Second)
    go loop(2)

    for i := 0; i < 2; i++ {
        <-quit
    }
}

对于调度器模型(M+P+G),我想我们只有 1 个 cpu 上下文,因为我们将 GOMAXPROCS 设置为 1,并且这里只有 1 个线程(M) .

在goroutine中,for循环没有任何IO阻塞,所以不会产生新线程,所有goroutine应该还在当前线程中工作,所以我认为2个goroutine必须一个一个走,因此,结果应该是 1 1 2 2。但实际上,结果是1 2 1 2。为什么?

这是你程序中相关操作的顺序。

  1. go loop(1) 调度 goroutine loop1
  2. time.Sleep(time.Second) 开始于 main goroutine
  3. fmt.Println(a) 从循环 1 调用, 打印 1
  4. loop1 进入忙循环并保持 CPU
  5. 从循环 1 进入 fmt.Println(a) 进入调度程序
  6. main goroutine 唤醒,完成 time.Sleep 调用
  7. go loop(2) 调度 goroutine loop2
  8. fmt.Println(a) 从循环 2 调用, 打印 2
  9. loop2进入忙循环并保持CPU
  10. 从循环 2 进入 fmt.Println(a) 进入调度程序
  11. loop1 唤醒并完成 fmt.Println(a)打印 1
  12. loop1 通过退出通道
  13. 发送 0
  14. loop2 完成 fmt.Println(a) 打印 2
  15. loop2 通过退出通道
  16. 发送 0