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. 小结
本讲不是为了凑字数,主要是为引入后续的抢占做个铺垫,下一讲会介绍运行时间过长的抢占调度。
Go runtime 调度器精讲(七):案例分析的更多相关文章
- Yarn 容量调度器多队列提交案例
目录 Yarn 容量调度器多队列提交案例 需求 配置多队列的容量调度器 1 修改如下配置 SecureCRT的上传和下载 2 上传到集群并分发 3 重启Yarn或yarn rmadmin -refre ...
- 【Cocos2d-x 3.x】 调度器Scheduler类源码分析
非个人的全部理解,部分摘自cocos官网教程,感谢cocos官网. 在<CCScheduler.h>头文件中,定义了关于调度器的五个类:Timer,TimerTargetSelector, ...
- Cocos2d-X3.0 刨根问底(六)----- 调度器Scheduler类源码分析
上一章,我们分析Node类的源码,在Node类里面耦合了一个 Scheduler 类的对象,这章我们就来剖析Cocos2d-x的调度器 Scheduler 类的源码,从源码中去了解它的实现与应用方法. ...
- 关于rt-thread调度器实现的底层代码分析
本文使用了rt-thread自带的钩子函数和显示函数进行了实验,从rt-thread自带的延时函数rt_thread_delay()函数入手,对rt-thread系统的调度器进行分析.主要参考资料 ...
- JavaScript特效制作经典精讲(案例入门详解、可直接粘贴拷贝运行、史上最牛案例)
技巧一.添加链接提示 <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http:// ...
- Python_装饰器精讲_33
from functools import wraps def wrapper(func): #func = holiday @wraps(func) def inner(*args,**kwargs ...
- mybatis精讲(七)--动态sql
目录 常用标签 if元素 choose元素 trim元素 forearch bind元素 在我们传统的开发中我们会通过拼接sql达到数据库的操作.java中的拼接不仅效率低下而且代码很长不易维护.而M ...
- 超精讲-逐例分析CS:LAB2-Bomb!(上)
0. 环境要求 关于环境已经在lab1里配置过了这里要记得安装gdb 安装命令 sudo yum install gdb 实验的下载地址 http://csapp.cs.cmu.edu/3e/labs ...
- 超精讲-逐例分析 CSAPP:实验2-Bomb!(下)
好了话不多说我们书接上文继续来做第二个实验下面是前半部分实验的连接 5. 第五关 首先感觉应该是个递归问题 /* Round and 'round in memory we go, where we ...
- Go语言goroutine调度器初始化(12)
本文是<Go语言调度器源代码情景分析>系列的第12篇,也是第二章的第2小节. 本章将以下面这个简单的Hello World程序为例,通过跟踪其从启动到退出这一完整的运行流程来分析Go语言调 ...
随机推荐
- 基于Java“花鸣”B2C电子商务平台设计实现(源码+lw+部署文档+讲解等)
\n文末获取源码联系 感兴趣的可以先收藏起来,大家在毕设选题,项目以及论文编写等相关问题都可以给我加好友咨询 系统介绍: 现代经济快节奏发展以及不断完善升级的信息化技术,让传统数据信息的管理升级为软件 ...
- oeasy教您玩转vim - 63 - # window分屏
窗口window 回忆上次 我们这次了解了缓冲区buffer ls可以查看buffer 如下是buffer缓冲的一些flag + 有修改未保存内容 - 可修改标签关闭 = 只读缓冲区 a 活跃缓冲 ...
- 使用uWSGI+nginx部署Django项目(Ubuntu)
对于uwsgi+nginx的部署方式,它的访问关系大概是: 1 the web client <-> the web server <-> the socket <-&g ...
- 新做了一个MySQL 数据库 DDL 差异对比的网站
MySQL 数据库 DDL 差异对比的网站 摘要 新做了个网站,用来对比不同环境下的 DDL 差异,生成变更点和 迁移 DDL 网站地址:https://ddlcompare.com/ 对比过程中如果 ...
- pidstat命令详解
pidstat命令详解 pidstat 命令是 sysstat 工具的一个命令,用来监控全部或者指定进程的CPU.内存.线程.设备IO等系统资源的占用情况.pidstat 首次运行时显示自系统启动开始 ...
- 测试环境配置https+端口访问留存
步骤1:阿里云DNS配置本地公网IP解析 步骤2:本地局域网192.168.1.10服务器配置nginx server { listen 8090 ssl; server_name localhost ...
- 支持国际学术资源开放(版权费用 Open Access),支持SCI-HUB,向Sci-hub致敬
在去年多次向中国红字会捐款后再次决定向公益事业捐款,这次的捐款对象是SCI-HUB,可以说这是我们这种弱势的无大单位庇佑的散researcher的必备工具,多年来一直在使用,这次突然看到有支付宝捐款的 ...
- 人形机器人(具身智能,Embodied Intelligence)—— 抓取动作(上半身动作规划)的各大公司技术路线
视频地址: https://www.youtube.com/watch?v=UZBSXzNKB1Q
- Linux系统——删除用户命令
背景: 负责管理实验室的服务器,近期有保研的大三同学放弃保送到实验室而选择其他实验室,因此需要把之前给他开的账号取消掉. ===================================== ...
- aarch64架构CPU下Ubuntu系统环境源码编译pytorch-gpu-2.0.1版本
准备事项: 1. pytorch源码下载: 源码的官方地址: https://github.com/pytorch/pytorch 但是这里我们不能简单的使用git clone命令下载,因为pytor ...