简析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 福建

相关推荐

11-24 00:11
已编辑
广东工业大学 算法工程师
避雷深圳&nbsp;&nbsp;yidao,试用期&nbsp;6&nbsp;个月。好嘛,试用期还没结束,就直接告诉你尽快找下一家吧,我谢谢您嘞
牛客75408465号:笑死,直属领导和 hr 口径都没统一,各自说了一些离谱的被裁理由,你们能不能认真一点呀,哈哈哈哈哈😅😅😅
点赞 评论 收藏
分享
头像
10-14 23:01
已编辑
中国地质大学(武汉) Java
CUG芝士圈:虽然是网上的项目,但最好还是包装一下,然后现在大部分公司都在忙校招,十月底、十一月初会好找一些。最后,boss才沟通100家,别焦虑,我去年暑假找第一段实习的时候沟通了500➕才有面试,校友加油
点赞 评论 收藏
分享
一颗宏心:华为HR晚上过了十二点后还给我法消息。
点赞 评论 收藏
分享
评论
1
1
分享
牛客网
牛客企业服务