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语言调 ...
随机推荐
- 基于CFX的小型风电机组流场计算流程
一.Workbench界面框架 二.Geometry模块操作 1.打开Geometry模块,导入txt格式模型 File >> Import External Geometry File ...
- element-plus如何隐藏el-row
在 Element Plus 中,el-row 是用于布局的组件,如果你想要隐藏 el-row,你可以使用 CSS 的 display 属性将其设置为 none.以下是一个简单的示例: <tem ...
- oeasy 教您玩转 linux 之 010302 火狐浏览器 firefox
我们来回顾一下 上一部分我们都讲了什么? oneko xeyes 这次看看这个火狐 火狐 看看当前版本 看看是否可以更新 如果需要更新就更新 firefox -v apt search f ...
- SEO初学指南之关键词研究(2) - 搜索意图分析
Hi,我是听风.这篇文章我们深入探讨下关键词研究中最重要的一点:搜索意图. 虽然这在本系列教程的第一篇中提到过搜索意图,但是我还是想再写一篇文章来帮助你们了解什么是搜索意图以及如何再关键词研究中使用它 ...
- 如何构建“集成神经网络”“Ensemble neural network”
参考: https://arxiv.org/pdf/1603.05691.pdf 本文要讨论的是如何构建"集成神经网络"("Ensemble neural network ...
- mojo编程语言:编译后的mojo二进制执行文件调用python库报错——设置MOJO_PYTHON_LIBRARY变量
代码: from python import Python fn f() raises: # This is equivalent to Python's `import numpy as np` l ...
- MPI4PY的数据类型 —— MPI4Py data type
原文地址: http://education.molssi.org/parallel-programming/03-distributed-examples-mpi4py/index.html MPI ...
- Quartz.NET 的使用
先貼使用代碼: 1 using Quartz; 2 using Quartz.Impl; 3 using Quartz.Logging; 4 using System; 5 using System. ...
- NVIDIA vGPU vApps/vWS/vCS适配GPU版本介绍
NVIDIA vGPU 12.0版本-vGPU版本名称变化 - 注: 2021年1月生效 最新名称 NVIDIA Virtual PC (vPC) -曾用名称 NVIDIA GRID Virtual ...
- 2022年第一天,体验了一把wan(皖)式服务
在新的一年到来之际, 一口君首先祝各位粉丝朋友新年快乐! 心想事成! 事业顺利! 阖家欢乐! 财源广进! 2022年的第一天,一口君带着家人去了躺马鞍山,享受了一下马鞍山的洗浴. 马鞍山桑拿虽然比不上 ...