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


0. 前言

第八讲介绍了当 goroutine 运行时间过长会被抢占的情况。这一讲继续看 goroutine 执行系统调用时间过长的抢占。

1. 系统调用时间过长的抢占

看下面的示例:

func longSyscall() {
timeout := syscall.NsecToTimeval(int64(5 * time.Second))
fds := make([]syscall.FdSet, 1) if _, err := syscall.Select(0, &fds[0], nil, nil, &timeout); err != nil {
fmt.Println("Error:", err)
} fmt.Println("Select returned after timeout")
} func main() {
threads := runtime.GOMAXPROCS(0)
for i := 0; i < threads; i++ {
go longSyscall()
} time.Sleep(8 * time.Second)
}

longSyscall goroutine 执行一个 5s 的系统调用,在系统调用过程中,sysmon 会监控 longSyscall,发现执行系统调用过长,会对其抢占。

回到 sysmon 线程看它是怎么抢占系统调用时间过长的 goroutine 的。

func sysmon() {
...
idle := 0 // how many cycles in succession we had not wokeup somebody
delay := uint32(0)
... for {
if idle == 0 { // start with 20us sleep...
delay = 20
} else if idle > 50 { // start doubling the sleep after 1ms...
delay *= 2
}
if delay > 10*1000 { // up to 10ms
delay = 10 * 1000
}
usleep(delay) ...
// retake P's blocked in syscalls
// and preempt long running G's
if retake(now) != 0 {
idle = 0
} else {
idle++
}
...
}
}

类似于运行时间过长的 goroutine,调用 retake 进行抢占:

func retake(now int64) uint32 {
n := 0
lock(&allpLock)
for i := 0; i < len(allp); i++ {
pp := allp[i]
if pp == nil {
continue
}
pd := &pp.sysmontick
s := pp.status
sysretake := false if s == _Prunning || s == _Psyscall { // goroutine 处于 _Prunning 或 _Psyscall 时会抢占
// Preempt G if it's running for too long.
t := int64(pp.schedtick)
if int64(pd.schedtick) != t {
pd.schedtick = uint32(t)
pd.schedwhen = now
} else if pd.schedwhen+forcePreemptNS <= now {
// 对于 _Prunning 或者 _Psyscall 运行时间过长的情况,都会进入 preemptone
// preemptone 我们在运行时间过长的抢占中介绍过,它主要设置了 goroutine 的标志位
// 对于处于系统调用的 goroutine,这么设置并不会抢占。因为线程一直处于系统调用状态
preemptone(pp)
// In case of syscall, preemptone() doesn't
// work, because there is no M wired to P.
sysretake = true
}
} if s == _Psyscall {
// Retake P from syscall if it's there for more than 1 sysmon tick (at least 20us).
// P 处于系统调用之中,需要检查是否需要抢占
// syscalltick 用于记录系统调用的次数,在完成系统调用之后加 1
t := int64(pp.syscalltick)
if !sysretake && int64(pd.syscalltick) != t {
// pd.syscalltick != pp.syscalltick,说明已经不是上次观察到的系统调用了,
// 而是另外一次系统调用,需要重新记录 tick 和 when 值
pd.syscalltick = uint32(t)
pd.syscallwhen = now
continue
} // On the one hand we don't want to retake Ps if there is no other work to do,
// but on the other hand we want to retake them eventually
// because they can prevent the sysmon thread from deep sleep.
// 如果满足下面三个条件的一个则执行抢占:
// 1. 线程绑定的本地队列中有可运行的 goroutine
// 2. 没有无所事事的 P(表示大家都挺忙的,那就不要执行系统调用那么长时间占资源了)
// 3. 执行系统调用时间超过 10ms 的
if runqempty(pp) && sched.nmspinning.Load()+sched.npidle.Load() > 0 && pd.syscallwhen+10*1000*1000 > now {
continue
} // 下面是执行抢占的逻辑
unlock(&allpLock)
// Need to decrement number of idle locked M's
// (pretending that one more is running) before the CAS.
// Otherwise the M from which we retake can exit the syscall,
// increment nmidle and report deadlock.
incidlelocked(-1)
if atomic.Cas(&pp.status, s, _Pidle) { // 将 P 的状态更新为 _Pidle
n++ // 抢占次数 + 1
pp.syscalltick++ // 系统调用抢占次数 + 1
handoffp(pp) // handoffp 抢占
}
incidlelocked(1)
lock(&allpLock)
}
}
unlock(&allpLock)
return uint32(n)
}

进入 handoffp

// Hands off P from syscall or locked M.
// Always runs without a P, so write barriers are not allowed.
//
//go:nowritebarrierrec
func handoffp(pp *p) {
// if it has local work, start it straight away
// 这里如果 P 的本地有工作(goroutine),或者全局有工作的话
// 将 P 和其它线程绑定,其它线程指的是不是执行系统调用的那个线程
// 执行系统调用的线程不需要 P 了,这时候把 P 释放出来,算是资源的合理利用,相比于线程,P 是有限的
if !runqempty(pp) || sched.runqsize != 0 {
startm(pp, false, false)
return
} ...
// no local work, check that there are no spinning/idle M's,
// otherwise our help is not required
if sched.nmspinning.Load()+sched.npidle.Load() == 0 && sched.nmspinning.CompareAndSwap(0, 1) { // TODO: fast atomic
sched.needspinning.Store(0)
startm(pp, true, false)
return
} ...
// 判断全局队列有没有工作要处理
if sched.runqsize != 0 {
unlock(&sched.lock)
startm(pp, false, false)
return
} ...
// 如果都没有工作,那就把 P 放到全局空闲队列中
pidleput(pp, 0)
unlock(&sched.lock)
}

可以看到抢占系统调用过长的 goroutine,这里抢占的意思是释放系统调用线程所绑定的 P,抢占的意思不是不让线程做系统调用,而是把 P 释放出来。(由于前面设置了这个 goroutine 的 stackguard0,类似于 运行时间过长 goroutine 的抢占 的流程还是会走一遍的)。

我们看一个示意图可以更直观清晰的了解这个过程:

handoff 结束之后,增加抢占次数 n,retake 返回:

func sysmon() {
...
idle := 0 // how many cycles in succession we had not wokeup somebody
delay := uint32(0)
for {
if idle == 0 { // start with 20us sleep...
delay = 20 // 如果 idle == 0,表示 sysmon 需要打起精神来,要隔 20us 监控一次
} else if idle > 50 { // start doubling the sleep after 1ms...
delay *= 2 // 如果 idle 大于 50,表示循环了 50 次都没有抢占,sysmon 将加倍休眠,比较空,sysmon 也不浪费资源,先睡一会
}
if delay > 10*1000 { // up to 10ms
delay = 10 * 1000 // 当然,不能无限制睡下去。最大休眠时间设置成 10ms
} if retake(now) != 0 {
idle = 0 // 有抢占,则 idle = 0,表示 sysmon 要忙起来
} else {
idle++ // 没有抢占,idle + 1
}
...
}
...
}

2. 小结

本讲介绍了系统调用时间过长引起的抢占。下一讲将继续介绍异步抢占。


Go runtime 调度器精讲(九):系统调用引起的抢占的更多相关文章

  1. Python_装饰器精讲_33

    from functools import wraps def wrapper(func): #func = holiday @wraps(func) def inner(*args,**kwargs ...

  2. 第三百五十九节,Python分布式爬虫打造搜索引擎Scrapy精讲—elasticsearch(搜索引擎)介绍以及安装

    第三百五十九节,Python分布式爬虫打造搜索引擎Scrapy精讲—elasticsearch(搜索引擎)介绍以及安装 elasticsearch(搜索引擎)介绍 ElasticSearch是一个基于 ...

  3. 第三百四十九节,Python分布式爬虫打造搜索引擎Scrapy精讲—cookie禁用、自动限速、自定义spider的settings,对抗反爬机制

    第三百四十九节,Python分布式爬虫打造搜索引擎Scrapy精讲—cookie禁用.自动限速.自定义spider的settings,对抗反爬机制 cookie禁用 就是在Scrapy的配置文件set ...

  4. Linux CFS调度器之task_tick_fair处理周期性调度器--Linux进程的管理与调度(二十九)

    1. CFS如何处理周期性调度器 周期性调度器的工作由scheduler_tick函数完成(定义在kernel/sched/core.c, line 2910), 在scheduler_tick中周期 ...

  5. Linux进程核心调度器之主调度器schedule--Linux进程的管理与调度(十九)

    主调度器 在内核中的许多地方, 如果要将CPU分配给与当前活动进程不同的另一个进程, 都会直接调用主调度器函数schedule, 从系统调用返回后, 内核也会检查当前进程是否设置了重调度标志TLF_N ...

  6. 第三百六十九节,Python分布式爬虫打造搜索引擎Scrapy精讲—elasticsearch(搜索引擎)用Django实现搜索功能

    第三百六十九节,Python分布式爬虫打造搜索引擎Scrapy精讲—elasticsearch(搜索引擎)用Django实现搜索功能 Django实现搜索功能 1.在Django配置搜索结果页的路由映 ...

  7. 第三百四十一节,Python分布式爬虫打造搜索引擎Scrapy精讲—编写spiders爬虫文件循环抓取内容—meta属性返回指定值给回调函数—Scrapy内置图片下载器

    第三百四十一节,Python分布式爬虫打造搜索引擎Scrapy精讲—编写spiders爬虫文件循环抓取内容—meta属性返回指定值给回调函数—Scrapy内置图片下载器 编写spiders爬虫文件循环 ...

  8. 第三百三十九节,Python分布式爬虫打造搜索引擎Scrapy精讲—Scrapy启动文件的配置—xpath表达式

    第三百三十九节,Python分布式爬虫打造搜索引擎Scrapy精讲—Scrapy启动文件的配置—xpath表达式 我们自定义一个main.py来作为启动文件 main.py #!/usr/bin/en ...

  9. CFS调度器

    一.前言 随着内核版本的演进,其源代码的膨胀速度也在递增,这让Linux的学习曲线变得越来越陡峭了.这对初识内核的同学而言当然不是什么好事情,满腔热情很容易被当头浇灭.我有一个循序渐进的方法,那就是先 ...

  10. Golang/Go goroutine调度器原理/实现【原】

    Go语言在2016年再次拿下TIBOE年度编程语言称号,这充分证明了Go语言这几年在全世界范围内的受欢迎程度.如果要对世界范围内的gopher发起一次“你究竟喜欢Go的哪一点”的调查,我相信很多Gop ...

随机推荐

  1. 女朋友问我 LB 是谁?

    科普一下 LB(负载均衡)技术 我的编程导航网站:www.code-nav.cn 大家好,我是鱼皮. 周末在家写代码,无意中跟女朋友提了下 LB,还说 LB 好的呱呱叫. 她笑了笑,问我 LB 是谁? ...

  2. [oeasy]python0070_ 字体样式_下划线_中划线_闪动效果_反相_取消效果

    字体样式 回忆上次内容 m 可以改变字体样式 0-10 之间设置的都是字体效果 0 复原 1 变亮 2 变暗 从3到10 又是什么效果 呢?? 真的可以blink闪烁吗? 3m 3m 实现斜体字的效果 ...

  3. oeasy教您玩转python - 007 - # 字符本质

    ​ 字符本质 回忆上次内容 hello world 不是从来就有的 来自于unix和c 虽然我们今天有各种先进的学习手段 最早的高级语言学习是从最早的那张打字机用纸的手写代码起源的 所以输出用的是 p ...

  4. memset函数&&bzro函数_C

    // Code file created by C Code Develop #include "ccd.h" #include "stdio.h" #incl ...

  5. 由delete语句引起的锁范围扩大

    由delete语句引起的锁范围扩大 阿里云月报中的一句话,出处:http://mysql.taobao.org/monthly/2022/01/01/ 但是Ghost Record是可以跟正常的Rec ...

  6. LeetCode122. 买卖股票的最佳时机 II

    题目链接:https://leetcode.cn/problems/best-time-to-buy-and-sell-stock-ii/description/ 题目叙述: 给你一个整数数组 pri ...

  7. 使用 @Audited 增强Spring Boot 应用程序的数据审计能力

    介绍 在Spring Boot开发的动态世界中,确保数据完整性和跟踪变化是至关重要的.实现这一目标的一个强大工具是@Audited注解.本文深入探讨了该注解的复杂性.其目的.实现步骤以及如何利用其功能 ...

  8. 【Java】线程池配置

    先看JUC包自带的一个资源 线程池执行器: 初始化参数如下 ThreadPoolExecutor threadPoolExecutor = new ThreadPoolExecutor( corePo ...

  9. SourceGenerator 生成db to class代码优化结果记录 二

    优化 在上一篇留下的 Dapper AOT 还有什么特别优化点的问题 在仔细阅读生成代码和源码之后,终于得到了答案 个人之前一直以为 Dapper AOT 只用了迭代器去实现,所以理应差不多实现代码却 ...

  10. 【转载】 解决 sudo echo x > 时的 Permission denied错误

    版权声明:本文为CSDN博主「Yannick_J」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明.原文链接:https://blog.csdn.net/don_chi ...