首页 > 编程语言 >Go 并发编程 - runtime 协程调度(三)

Go 并发编程 - runtime 协程调度(三)

时间:2023-08-23 16:56:01浏览次数:32  
标签:协程 队列 go Go runtime CPU

Go Runtime

Go runtime 可以形象的理解为 Go 程序运行时的环境,类似于 JVM。不同于 JVM 的是,Go 的 runtime 与业务程序直接打包在一块,是一个可执行文件,直接运行在操作系统上,效率很高。

runtime 包含了一些 Go 的一些非常核心的功能:协程调度、垃圾回收、内存分配等。本文将着重介绍协程调度(GMP 模型)。

协程调度

协程调度是指 Go 如何管理和执行协程,Go 的协程调度基于 GMP 模型。即:

  • G (Goroutine):即 Go 的协程,包含了栈信息,代码指针,状态等;
  • M (Machine):代表一个工作线程,由操作系统直接分配;
  • P (Processor):处理器(Go 定义的一个概念,不是指 CPU),包含了协程运行的所需资源,如本地队列、全局队列、计数器等。

GMP 三者的关系:

  • P 的个数取决于设置的 runtime.GOMAXPROCS,默认是物理 CPU 的逻辑核心数量,比如四核八线程的 CPU,P 的数量就是 8;
  • M 的数量一般是多于 P 的,M 要想被 CPU 执行,必须先获取 P。没有获取 P 的 M,则处于休眠状态;
  • G 可以理解为代码本体,G 必须要被 P 调度进入 M,才可以被 CPU 执行;
  • P 包含了一个 LRQ (Local Run Queue)本地运行队列,保存着等待执行的协程(G)队列。没有被分配到 P 的 G,会被保存到 GRQ (Global Run Queue) 全局队列中,处于休眠状态。

假如主机是单逻辑 CPU 的,那么 GMP 是这样的:

红色部分表示休眠或者挂起状态,黄色代表等待执行,绿色表示正在运行。系统初始化了两个线程,但我们只有一个处理器(P), M1 没有获取到 P,所以只能休眠。M0 当前获取到 P ,正在处理 G0, LRQ 里面目前有三个 G 在排队等待被 M 运行,GRQ 里面保存着 G4、G5、G6,表示它们还没有分配到队列中。

P 这个时候会分别对 LRQ 进行周期队列轮转 和 GRO 周期性检查:

  • 队列轮转:LRQ 中的 G 被 P 调度到 M 中执行,每个 G 执行一段时间后,就会保存其上下文并放入队列尾部,然后取出队列头部的 G 进入 M 执行。
  • 周期性检查:P 会检查 GRQ 中是否有 G 正在等待运行,并将其放入 M 中执行,防止协程被饿死。
  • 在队列轮转中,如果当前正在运行的 G 遇到了系统调用,那么系统就会挂起当前 M0,释放 P,M1 就会绑定释放的 P,来继续执行其他协程。

假设 G0 遇到了系统调用:

等到 M1 中所有的协程执行完或者 M1 处理某个协程也遇到了了系统调用,就会重新释放 P 给其他空闲的 M。而另外一边 G0 的系统调用结束后,就会将 M0 线程从挂起状态变成休眠状态,并将 G0 放入 GRQ,等待被 P 重新调入 LRQ 中轮转执行。

如果我们的主机具备多个逻辑 CPU,创建了多个 P,那么就会变成多个线程并行执行:

多线程同时处理时,很有可能多个 LRQ 是不均衡的。假如上图的 M0 已经执行完了,其他线程还处于繁忙状态,M0 所绑定的 P 就会去检查 GQR,GQR 中也没有 G,那么它就会去偷取其他 LRQ 一部分的 G 来执行,一般每次会偷取一半。

runtime 包

runtime.GOMAXPROCS

 runtime.GOMAXPROCS() 可以用来设置 P 的数量,一般设置为和逻辑 CPU  数量相等的值:

fmt.Println(runtime.NumCPU())
runtime.GOMAXPROCS(runtime.NumCPU()) // 使用所有的逻辑 CPU

// 结果
我的主机 CPU 是16核24线程,所以会使用24个 P

runtime.Gosched

runtime.Gosched() 用于让出当前协程的运行时间片,也就是当 P 遇到它时,会先安排其他协程先执行:

func main() {
	runtime.GOMAXPROCS(1)
	go func() {
		for i := 0; i < 5; i++ {
			fmt.Println("go")
		}
	}()

	runtime.Gosched()
	fmt.Println("hello")
}


// 结果
输入结果不是固定的,有可能是
go
go
go
go
go
hello
也有可能是
go
go
go
go
hello
go
也有可能是
hello

输出第一种情况容易理解,主协程让出了时间片,理所应当先打印 Go,但是如果子协程还没有来得及被调度或者打印,就会出现其他情况。

runtime.Goexit

runtime.Goexit() 会结束当前的协程,但是 defer 语句会正常执行。此语法不能在主函数中使用,会引发 panic:

func main() {
	runtime.GOMAXPROCS(1)
	go func() {
		defer fmt.Println("defer不受影响")
		fmt.Println("我被执行了")
		runtime.Goexit()
		fmt.Println("我被跳过了")
	}()

	time.Sleep(1 * time.Second)
}

// 结果
我被执行了
defer不受影响

本系列文章:

  1. Go 并发编程 - Goroutine 基础 (一)
  2. Go 并发编程 - 并发安全(二)
  3. Go 并发编程 - runtime 协程调度(三)

标签:协程,队列,go,Go,runtime,CPU
From: https://www.cnblogs.com/oldme/p/17652132.html

相关文章

  • gorm的First、Find、Raw与Scan
    使用First,Take,Last进行单条查找,First根据主键正序,Last根据主键倒序Find查询结果是列表,First查询的是单条数据。Raw中文:原生的作用:在写gorm语句时候用来写Rawsql语句(原生sql语句)在使用Raw自定义SQL查询时,使用Scan来接收数据,虽然Find也是可以接收的,但是Find主要还是用来带......
  • Django-CKEditor富文本编译器相关知识介绍
    安装pipinstalldjango-ckeditorpipinstallpillow注册富文本编译器在settings中的INSTALLED_APPS代码块中加入ckeditor和ckeditor_uploader(可支持图片上传)配置富文本编译器在settings中增加以下代码:#指定富文本编辑器或其他上传文件的根目录,这里为/test_blog/media......
  • slice 切片数组测试记录【GO 基础】
    〇、测试前准备本文是在GO环境下测试记录系列之一,GO基本环境部署步骤将略过,直接上代码。下面是常用命令:【初始化+运行+编译】//{GOPATH}环境变量值,example项目文件夹名称{GOPATH}\src\example>//运行代码//xxx.go为文件全名gorunxxx.go//初始化//重复......
  • python-django-ORM-F查询和Q查询
    F查询如果要比较一个表中的两个不同的字段,可以使用F查询importosif__name__=='__main__':#加载Django项目的配置信息os.environ.setdefault("DJANGO_SETTINGS_MODULE","mysite2.settings")#导入Django,并启动Django项目importdjangod......
  • Kanban:促进价值流动的艺术-Leangoo领歌免费敏捷看板工具
    ​转自:本文转自Leangoo领歌敏捷工具:https://www.leangoo.com刚接触敏捷转型的团队对Kanban和Scrum的基本概念和应用环境都不陌生,但都是作为被动接受者来理解和使用的,这篇文章的目的是希望基于底层逻辑来阐述看板的艺术来帮助团队加深理解,从而帮助ScrumMaster在带领团队的......
  • WebDriver.__init__() got an unexpected keyword argument 'desired_capabilities'
    我的selenium的版本是4.11.2selenium4.10中已经不支持desired_capabilities参数如果要传这个参数的话建议用selenium==4.9.1参考《Appium新版本引发的一个问题》......
  • GO、KO、EGGNOG、RXN、PFAM和Level4EC
    这些数据库在生物学和分子生物学领域中发挥着关键作用,用于注释和分类基因和蛋白质功能,以及理解生物系统的运作方式。GeneOntology(GO):简介:GO是一种用于描述基因和蛋白质功能的标准化分类体系。它将基因和蛋白质的功能划分为分子功能、细胞组分和生物学过程三个大类,每个类别......
  • Google Chrome和ChromeDriver版本号不一致问题解决
    1(base)kaka@KakadeMBPbin%/Applications/Google\Chrome.app/Contents/MacOS/Google\Chrome--version2GoogleChrome116.0.5845.963(base)kaka@KakadeMBPbin%chromedriver--version4ChromeDriver114.0.5735.16(7e1ff058633f5b79b1cd7479aca585ba385......
  • Go-运算符
    1运算符分类1算数运算符:+,-,*,/,%,++,--2赋值运算符:=,+=,-=,*=,/=,%=3关系运算符:==,!=,<,>,<=,>=4逻辑运算符:&&,||,!5位运算符:&,|,^6其他运算符:&,*2算数运算符:2.1”+“示例packagemainimport"fmt"funcmain(){ //"+"可以表示正数 varx=......
  • 在Mac系统上构建适用于Linux 64位的Go程序
    要在Mac系统上构建适用于Linux64位的Go程序,可以采用以下2种方式:1.通过设置环境变量并使用交叉编译来实现以下是在Mac系统上构建适用于Linux64位的Go程序的步骤:在你的项目根目录下,打开终端。设置环境变量GOOS和GOARCH为linux和amd64,分别表示目标操作系统为Linux,目......