简析goroutine调度原理

相较于操作系统线程,goroutine的资源占用和使用代价要小得多。可以创建成百上万甚至上千万个goroutine。Go运行时负责对goroutine进行调度惯例,即决定何时哪个goroutine获得资源执行,哪个goroutine停止执行让出资源,哪个goroutine被唤醒恢复执行等。

goroutine是Go语言并发的基础,也是最基本的执行单元。Go基于goroutine建立了GPM调度模型。

G:goroutine在go运行时中的抽象模型。代表goroutine,存储了goroutine的执行栈信息、goroutine状态及goroutine函数信息,G对象是可以重复使用的。

P:G和M之间的一个中间层。P的数量决定了系统内最大可并行的G数量(P数量 < CPU核数)

M: 对操作系统线程(被视为”物理CPU“)的一种抽象,代表这真正的计算资源

G想要运行起来,首先需要分配一个P,即进入P的本地运行队列中(local runq),P又和M绑定,P和M绑定后P的本地队列中的G才能运行起来。P和M的关系类似Linux操作系统调度层面用户线程与内核线程的对应关系:多对多。

Go调度器实现了抢占式调度,调度到死循环后不会一直执行下去,即G不会一直占用分配给它的P和M,位于P的本地队列中的其他G可以得到调度。

import (
	"fmt"
	"time"
)

func endlessLoop1() {
	for {
		time.Sleep(1 * time.Second)
		fmt.Println("I am endlessLoop1")
	}
}

func endlessLoop2() {
	for {
		time.Sleep(1 * time.Second)
		fmt.Println("I am endlessLoop2")
	}
}

func main() {
    runtime.GOMAXPROCS(1)  // 设置P的数量,此处为1,即多个goroutine都加入一个P的本地队列中等待被调度
	go endlessLoop1()
	go endlessLoop2()

	for {
		time.Sleep(1 * time.Second)
		fmt.Println("I can be arrived!")
	}
}

上边代码中启动了三个goroutine:一个main goroutine,其中for循环为死循环。两个endlessLoop goroutine,endlessLoop也是一个死循环。下面运行程序

go run go-preempt-scheduler.go
I can be arrived!
I am endlessLoop1
I am endlessLoop2
I can be arrived!
I am endlessLoop1
...

从运行结果可以看到,三个在一个P的队列中的死循环goroutine都得到了调度。

Go语言基础及实战 文章被收录于专栏

Go语言学习笔记、语法知识、技术要点和个人理解及实战

全部评论
大佬nb
点赞 回复 分享
发布于 2023-04-09 23:15 福建

相关推荐

喜欢吃蛋糕仰泳鲈鱼是我的神:字节可以找个hr 给你挂了,再放池子捞
点赞 评论 收藏
分享
一名愚蠢的人类:多少games小鬼留下了羡慕的泪水
投递荣耀等公司10个岗位
点赞 评论 收藏
分享
1 1 评论
分享
牛客网
牛客企业服务