首页 > 其他分享 >Go runtime 调度器精讲(七):案例分析

Go runtime 调度器精讲(七):案例分析

时间:2024-09-15 18:25:06浏览次数:1  
标签:异步 main 抢占 精讲 go time Go runtime

原创文章,欢迎转载,转载请注明出处,谢谢。


0. 前言

前面用了六讲介绍 Go runtime 调度器,这一讲我们看一个关于调度 goroutine 的程序案例分析下调度器做了什么。需要说明的是,这个程序和抢占有关,抢占目前为止还没有介绍到,如果看不懂也没有关系,有个印象就行。

1. 案例 1

执行代码:

func gpm() {
	var x int
	for {
		x++
	}
}

func main() {
	var x int
	threads := runtime.GOMAXPROCS(0)
	for i := 0; i < threads; i++ {
		go gpm()
	}

	time.Sleep(1 * time.Second)
	fmt.Println("x = ", x)
}

运行程序:

# go run main.go 
x =  0

(为什么输出 x=0 和本系列内容无关,这里直接跳过)

Go 在 1.14 版本引入了异步抢占机制,我们使用的是 1.21.0 版本的 Go,默认开启异步抢占。通过 asyncpreemptoff 标志可以开启/禁用异步抢占,asyncpreemptoff=1 表示禁用异步抢占,相应的 asyncpreemptoff=0 表示开启异步抢占。

1.1 禁用异步抢占

首先,禁用异步抢占,再次执行上述代码:

# GODEBUG=asyncpreemptoff=1 go run main.go

程序卡死,无输出。查看 CPU 使用率:

top - 10:08:53 up 86 days, 10:48,  0 users,  load average: 3.08, 1.29, 0.56
Tasks: 179 total,   2 running, 177 sleeping,   0 stopped,   0 zombie
%Cpu(s): 74.4 us,  0.6 sy,  0.0 ni, 25.0 id,  0.0 wa,  0.0 hi,  0.0 si,  0.0 st
MiB Mem :  20074.9 total,   4279.4 free,   3118.3 used,  12677.2 buff/cache
MiB Swap:      0.0 total,      0.0 free,      0.0 used.  16781.0 avail Mem 

    PID USER      PR  NI    VIRT    RES    SHR S  %CPU  %MEM     TIME+ COMMAND                                 
1014008 root      20   0 1226288    944    668 R 293.7   0.0   5:35.81 main             // main 是执行的进程

CPU 占用率高达 293.7,太高了。

为什么会出现这样的情况呢?我们可以通过 GODEBUG=schedtrace=1000,scheddetail=1,asyncpreemptoff=1 打印程序执行的 G,P,M 信息,通过 DEBUG 输出查看调度过程中发生了什么。

当创建和线程数相等的 goroutine 后,线程执行 main goroutine。runtime(实际是 sysmon 线程,后文会讲)发现 main goroutine 运行时间过长,把它调度走,运行其它 goroutine(这是主动调度的逻辑,不属于异步抢占的范畴)。接着执行和线程数相等的 goroutine,这几个 goroutine 是永不退出的,线程会一直执行,占满逻辑核。

解决这个问题,我们改动代码如下:

func main() {
	var x int
	threads := runtime.GOMAXPROCS(0)
	for i := 0; i < threads; i++ {
		go gpm()
	}

	time.Sleep(1 * time.Nanosecond)
	fmt.Println("x = ", x)
}

因为 main goroutine 运行时间过长,被 runtime 调度走。我们把休眠时间设成 1 纳秒,不让它睡那么长。接着执行程序:

# GODEBUG=asyncpreemptoff=1 go run main.go 
x =  0

程序退出。天下武功唯快不破啊,main goroutine 直接执行完退出,不给 runtime 反应的机会。

还有其它改法吗?我们在 gpm 中加上 time.Sleep 函数调用:

func gpm() {
	var x int
	for {
		time.Sleep(1 * time.Nanosecond)
		x++
	}
}

func main() {
	var x int
	threads := runtime.GOMAXPROCS(0)
	for i := 0; i < threads; i++ {
		go gpm()
	}

	time.Sleep(1 * time.Second)
	fmt.Println("x = ", x)
}

运行程序:

# GODEBUG=asyncpreemptoff=1 go run main.go 
x =  0

也是正常退出。为什么加上函数调用就可以呢?这和抢占的逻辑有关,因为有了函数调用,就有机会在函数序言部分设置“抢占标志”,执行抢占 goroutine 的调度(同样的,后面会详细讲)。

要注意这里 time.Sleep(1 * time.Nanosecond) 加的位置,如果加在这里:

func gpm() {
	var x int
	time.Sleep(1 * time.Nanosecond)
	for {
		x++
	}
}

程序还是会卡死。

我们讨论了半天 asyncpreemptoff=1 禁止异步抢占的情况。是时候开启异步抢占看看输出结果了。

1.2 开启异步抢占

程序还是那个程序:

func gpm() {
	var x int
	for {
		x++
	}
}

func main() {
	var x int
	threads := runtime.GOMAXPROCS(0)
	for i := 0; i < threads; i++ {
		go gpm()
	}

	time.Sleep(1 * time.Second)
	fmt.Println("x = ", x)
}

开启异步抢占执行:

# GODEBUG=asyncpreemptoff=0 go run main.go 
x =  0

异步抢占就可以了,为啥异步抢占就可以了呢?异步抢占通过给线程发信号的方式,使得线程在“安全点”执行异步抢占的逻辑(后面几讲会介绍异步抢占的逻辑)。

再次改写代码如下:

//go:nosplit
func gpm() {
	var x int
	for {
		x++
	}
}

func main() {
	var x int
	threads := runtime.GOMAXPROCS(0)
	for i := 0; i < threads; i++ {
		go gpm()
	}

	time.Sleep(1 * time.Second)
	fmt.Println("x = ", x)
}

同样的执行输出:

# GODEBUG=asyncpreemptoff=0 go run main.go 

程序又卡死了...

这个程序就当思考题吧,为什么加个 //go:nosplit 程序就卡死了呢?

2. 小结

本讲不是为了凑字数,主要是为引入后续的抢占做个铺垫,下一讲会介绍运行时间过长的抢占调度。


标签:异步,main,抢占,精讲,go,time,Go,runtime
From: https://www.cnblogs.com/xingzheanan/p/18415503

相关文章

  • Go几种内存逃逸的情况
    main.gopackagemainimport"fmt"//指针(任何类型的指针)、slice和map作为返回值//当带有指针的返回值被赋给外部变量或者作为参数传递给其他函数时,编译器无法确定该变量何时停止使用//因此,为了确保安全性和正确性,它必须将该数据分配在堆上,并使其逃离当前函数作用域func......
  • Django的高校学生信息管理系统的设计与实现-附源码02553
    摘 要本研究旨在设计和实现基于Django框架的高校学生信息管理系统,涵盖了系统用户、学生信息管理、教师信息管理、课程分类管理、开课信息管理、选课信息管理、课表信息管理、成绩信息管理、系统管理、网站公告管理和校园资讯等多个功能模块。通过系统的建立,旨在提供一个全面......
  • 基于django+vue店铺供应链系统【开题报告+程序+论文】-计算机毕设
    本系统(程序+源码+数据库+调试部署+开发环境)带论文文档1万字以上,文末可获取,系统界面在最后面。系统程序文件列表开题报告内容研究背景随着电子商务的蓬勃发展和市场竞争的日益激烈,店铺供应链系统的优化与升级成为了零售企业提升竞争力的关键。传统的供应链管理方式已难以满......
  • 基于django+vue电子招投标系统【开题报告+程序+论文】-计算机毕设
    本系统(程序+源码+数据库+调试部署+开发环境)带论文文档1万字以上,文末可获取,系统界面在最后面。系统程序文件列表开题报告内容研究背景随着信息技术的飞速发展,电子化、网络化已成为各行各业转型升级的重要趋势。在招投标领域,传统的手工操作与纸质文档管理方式已难以满足高效......
  • 基于django+vue电子相册管理系统【开题报告+程序+论文】-计算机毕设
    本系统(程序+源码+数据库+调试部署+开发环境)带论文文档1万字以上,文末可获取,系统界面在最后面。系统程序文件列表开题报告内容研究背景随着数字技术的飞速发展,人们日常生活中拍摄的照片数量急剧增加,如何高效、有序地管理和存储这些珍贵的记忆成为了亟待解决的问题。传统的纸......
  • 基于django+vue电子商务网站的设计与实现【开题报告+程序+论文】-计算机毕设
    本系统(程序+源码+数据库+调试部署+开发环境)带论文文档1万字以上,文末可获取,系统界面在最后面。系统程序文件列表开题报告内容研究背景随着互联网技术的飞速发展和普及,电子商务已成为全球经济的重要组成部分,深刻改变着人们的消费习惯与商业模式。电子商务网站作为企业与消费......
  • Go协程及并发锁应用指南
    概念协程(Goroutine)是Go语言独有的并发体,是一种轻量级的线程,也被称为用户态线程。相对于传统的多线程编程,协程的优点在于更加轻量级,占用系统资源更少,切换上下文的速度更快,不需要像多线程编程一样处理锁等线程安全问题。1.协程的创建在Go语言中,可以使用go语句来启动一个......
  • 【开题报告】基于django+vue物流管理系统(论文+程序)
    本系统(程序+源码+数据库+调试部署+开发环境)带论文文档1万字以上,文末可获取,系统界面在最后面。系统程序文件列表开题报告内容研究背景随着电子商务的蓬勃发展和消费者购物习惯的转变,物流行业迎来了前所未有的发展机遇与挑战。传统的物流管理系统往往存在信息孤岛、效率低下......
  • 【Go - 超实用,3行代码实现个自增器】
    场景自增器的作用是生成一个唯一的递增序列号。这在一些需要生成自增id的场景十分有用,比如自增的订单号,任务号,序列号。要点全局统一:在整个服务体系下,多个服务或者进程,都统一调用这个自增器,来获取自增ID。严格自增:避免竞争,写冲突造成写覆盖等,导致不严格自增实现根据上面......
  • 【开题报告】基于django+vue旅游管理系统(论文+源码) 计算机毕业设计
    本系统(程序+源码+数据库+调试部署+开发环境)带论文文档1万字以上,文末可获取,系统界面在最后面。系统程序文件列表开题报告内容研究背景随着旅游业的蓬勃发展,旅游市场的竞争日益激烈,传统的旅游管理方式已难以满足游客多元化、个性化的需求。在这个数字化时代,构建一个高效、便......