在 Go 语言中, GMP 模型中的 P(processor) 管理了若干个 Goroutine ,而这些 Goroutine 最终都运行在 M(machine)上。在 GMP 模型中,当一个 P 管理的 Goroutine 阻塞时,为了让其他 Goroutine 继续运行,这个 P 就会从其他 P 管理的 Goroutine 中偷取一些 Goroutine,然后放到自己的队列中运行。
在这个偷取 Goroutine 的过程中,由于 Goroutine 的状态是通过轻量级的原子操作来改变的,而不是通过加锁来保护的,所以偷取 Goroutine 不需要加锁。
下面是一个简单的示例代码,演示了当一个 Goroutine 阻塞时,偷取 Goroutine 的过程:
package main
import (
"fmt"
"runtime"
)
func main() {
runtime.GOMAXPROCS(2) // 设置 P 的数量为 2
ch := make(chan int, 2)
go func() {
for i := 0; i < 10; i++ {
ch <- i
fmt.Println("send: ", i)
}
}()
go func() {
for {
select {
case i := <-ch:
fmt.Println("recv: ", i)
}
}
}()
select {}
}
在这个示例中,我们启动了两个 Goroutine ,其中一个往一个带缓冲的通道中发送数值,另一个从通道中接收数值并打印。由于通道中有缓冲,所以发送操作不会阻塞,但是接收操作会一直阻塞,直到通道中有数据。
在这个过程中,由于只有一个 P ,所以一旦接收 Goroutine 阻塞,整个程序就会阻塞。为了解决这个问题,Go 运行时系统会自动将这个阻塞的 Goroutine 从当前 P 的队列中偷取,并放到另一个 P 的队列中运行,这样就可以让程序继续运行。在这个过程中,并没有加锁的操作,而是使用了轻量级的原子操作来实现。