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

news/2025/1/15 15:07:36/文章来源:https://www.cnblogs.com/xingzheanan/p/18415503

0. 前言

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

1. 案例 1

执行代码:

func gpm() {var x intfor {x++}
}func main() {var x intthreads := 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 intthreads := 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 intfor {time.Sleep(1 * time.Nanosecond)x++}
}func main() {var x intthreads := 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 inttime.Sleep(1 * time.Nanosecond)for {x++}
}

程序还是会卡死。

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

1.2 开启异步抢占

程序还是那个程序:

func gpm() {var x intfor {x++}
}func main() {var x intthreads := 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 intfor {x++}
}func main() {var x intthreads := 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. 小结

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


本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.hqwc.cn/news/797702.html

如若内容造成侵权/违法违规/事实不符,请联系编程知识网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

Ubuntu Server 24.04 LTS 安装记事

Ubuntu Server 24.04 LTS,代号 Noble Numbat,于 2024 年 4 月 25 日正式发布,该发布版可以支持长达12年。 曾经接触Ubuntu的时候,还是它的第一个版本,但是那个时候网络太局限了,关于这个系统的书籍也很少,所以就一直没有使用起来。 有时候好像什么事情都得有个名头,就像…

帝国cms密码忘记,帝国cms忘记登陆账号密码

如果你忘记了帝国CMS的管理员账号和密码,可以通过以下几种方法来找回或重置密码: 1. 通过数据库直接修改密码 如果你对数据库操作比较熟悉,可以采用此方法:登录数据库管理工具:使用phpMyAdmin或其他数据库管理工具登录到你的数据库。 找到管理员表:在数据库中找到存储管理…

1张超级“支付清算架构”图

在支付行业的快速发展中,理解和掌握支付清算架构对于从业人员来说至关重要。本文将通过一张精心绘制的“超级支付清算架构图”,带领读者深入探索支付生态的全貌。这张图不仅包含了丰富的支付组织、系统建设和账户基础等信息,而且通过高维度抽象,展示了它们之间复杂的交互关…

迅睿cms后台密码忘记了,如何重置找回密码

如果你忘记了迅睿CMS的后台密码,可以通过以下几种方法来重置或找回密码: 1. 通过邮箱找回密码 如果迅睿CMS支持通过邮箱找回密码,并且你在注册时提供了有效的邮箱地址,你可以尝试以下步骤:访问登录页面:前往迅睿CMS的后台登录页面。 点击“忘记密码”:在登录页面上寻找“…

DedeCMS系统管理员帐号密码忘记了怎么办

如果你忘记了DedeCMS(织梦内容管理系统)的管理员账号密码,可以通过以下几种方法来找回或重置密码: 1. 通过数据库直接修改密码登录数据库管理工具:使用phpMyAdmin或其他数据库管理工具登录到你的数据库。 找到管理员表:在数据库中找到存储管理员信息的表。对于DedeCMS来说…

利用未标记数据的半监督学习在模型训练中的效果评估

数据科学家在实践中经常面临的一个关键挑战是缺乏足够的标记数据来训练可靠且准确的模型。标记数据对于监督学习任务(如分类或回归)至关重要。但是在许多领域,获取标记数据往往成本高昂、耗时或不切实际。相比之下,未标记数据通常较易获取,但无法直接用于模型训练。 如何利…

如何利用帝国CMS搭建多个网站?

使用帝国CMS搭建多个网站可以通过多站点功能来实现。帝国CMS支持在一个主系统中管理多个独立的网站,这种方式不仅节省了服务器资源,还便于集中管理。以下是使用帝国CMS搭建多个网站的步骤: 1. 安装帝国CMS主系统下载安装包:从帝国CMS官方网站下载最新版本的安装包。 上传文…

mysql数据怎么导入到帝国cms

将MySQL数据导入到帝国CMS中通常有两种情况:一种是从现有的MySQL数据库导入数据到帝国CMS的新建数据库中,另一种是从帝国CMS的备份文件恢复数据到现有的帝国CMS数据库中。以下是针对这两种情况的具体步骤: 从现有MySQL数据库导入数据到帝国CMS 方法一:手动迁移数据导出现有…

如何找到并打开帝国CMS的数据库文件?

通过数据库管理工具访问使用phpMyAdmin或其他数据库管理工具:登录到你的数据库管理工具,比如phpMyAdmin。 在左侧边栏中,找到你的数据库列表。 选择你用来存储帝国CMS数据的数据库。查找数据库配置文件 为了确定具体的数据库名称,你可以查找帝国CMS的数据库配置文件。对于帝…

如何解决帝国CMS无法连接数据库的问题?

当帝国CMS无法连接到数据库时,通常是由于配置问题、数据库服务问题或网络问题等原因造成的。以下是一些排查和解决问题的方法: 1. 检查数据库配置 确保在帝国CMS的配置文件中正确设置了数据库连接信息。对于帝国CMS 7.0及以后版本,数据库配置文件通常位于:数据库配置文件位…

帝国CMS备份还原数据库

帝国CMS(EmpireCMS,简称ECMS)提供了内置的备份与恢复工具,允许用户轻松地备份和恢复数据库。以下是使用帝国CMS后台工具进行数据库备份和恢复的步骤,以及一些额外的方法。 使用帝国CMS后台工具备份和恢复数据库 备份数据库登录帝国CMS后台:使用管理员账号登录帝国CMS的管…