【golang】调度系列之sysmon

news2024/12/26 0:02:12

调度系列
调度系列之goroutine
调度系列之m
调度系列之p

在golang的调度体系中,除了GMP本身,还有另外一个比较重要的角色sysmon。实际上,除了GMP和sysmon,runtime中还有一个全局的调度器对象。但该对象只是维护一些全局的数据,而不承担实际的调度职责,并不值得单独介绍,感兴趣的同学可以自己了解一下。

回到sysmon,sysmon是一个管理线程或者说守护线程,其是对GMP调度架构的补充和兜底。通过前面的几篇介绍,可以知道GMP的调度完全是主动协作式的调度。主动协作式的调度性能很高,但是在某些情况下会出现单个goroutine长期占据时间片甚至一直占据时间片的情况。
比如:

  • 某个goroutine不执行主动调度、不调用系统调用、不做函数调用,就会一直运行直到goroutine退出;
  • 某个goroutine处于syscall状态时也无法触发主动调度,可能会造成该goroutine长时间占据时间片;

sysmon的作用就是处理类似上面情况,其主要的工作内容有:

  • 定期查看netpoll有无就绪的任务,防止netpoll阻塞队列中的goroutine饥饿;
  • 定期查看是否有p长时间(10ms)处于syscall状态,如有则将p的持有权释放以执行其他g;
  • 定期查看是否有p长时间(10ms)没有调度,如有则对当前m发送信号,触发基于信号的异步抢占调度;

在main函数启动时,会调用newm函数创建sysmon线程,sysmon作为mstartfn传入。

// src/runtime/proc.go 145
// The main goroutine.
func main() {
    ...
    if GOARCH != "wasm" { // no threads on wasm yet, so no sysmon
       systemstack(func() {
          newm(sysmon, nil, -1)
       })
    }
    ...
}

在介绍m的时候,我们提到过,mstart中会先调用mstartfn,然后再获取p并调用schedule函数。由于sysmon函数是循环不返回的,所以对应的m(也就是线程)永远运行sysmon,并且不需要获取p。所以并不是所有的m都需要p才可以运行的。
接下来,我们看下sysmon的里面具体做了些什么。

  • 进入sysmon可以看到里面是一个死循环,这和我们上面提到的一样。该循环并非一直忙等,而是会根据系统的情况进行延时睡眠,初始的interval是20us,最大的interval是10ms。
  • 在某些特殊的情况,sysmon可以进入更长时间(超过10ms)的睡眠,条件包括:
    • 系统不需要schedtrace。看起来是和调度相关观测的内容,如果需要schedtrace,则sysmon需要及时输出相关数据;
    • 系统处于停滞状态。这个停滞是我自己描述的,不一定准确,包括两种情况:1. 所有的p都是空闲的,此时系统中没有任务执行;2. 系统在等待进入gc状态,马上要stop the world;
      满足上面两个条件,则可最大进行1min的睡眠。1min是最大强制gc时间(2min)的一半。
  • sysmon的活跃状态,首先会坚持netpoll是否超过10ms没有被检查过,这是为了防止netpoll挂载goroutine的饥饿;
  • 然后会进行retake操作,retake的内容就是对所有p进行检查,查看p是否处于syscall或者被一个goroutine占据时间过长(超过10ms),如果有则进行相应的处理;
  • 最后还会进行gc和schedtrace相关的操作;
// src/runtime.go 5134
func sysmon() {
    lock(&sched.lock)
    sched.nmsys++
    checkdead()
    unlock(&sched.lock)

    lasttrace := int64(0)
    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)

       now := nanotime()
       if debug.schedtrace <= 0 && (sched.gcwaiting != 0 || atomic.Load(&sched.npidle) == uint32(gomaxprocs)) {
          lock(&sched.lock)
          if atomic.Load(&sched.gcwaiting) != 0 || atomic.Load(&sched.npidle) == uint32(gomaxprocs) {
             syscallWake := false
             next := timeSleepUntil()
             if next > now {
                atomic.Store(&sched.sysmonwait, 1)
                unlock(&sched.lock)
                // Make wake-up period small enough
                // for the sampling to be correct.
                sleep := forcegcperiod / 2
                if next-now < sleep {
                   sleep = next - now
                }
                shouldRelax := sleep >= osRelaxMinNS
                if shouldRelax {
                   osRelax(true)
                }
                syscallWake = notetsleep(&sched.sysmonnote, sleep)
                if shouldRelax {
                   osRelax(false)
                }
                lock(&sched.lock)
                atomic.Store(&sched.sysmonwait, 0)
                noteclear(&sched.sysmonnote)
             }
             if syscallWake {
                idle = 0
                delay = 20
             }
          }
          unlock(&sched.lock)
       }

       lock(&sched.sysmonlock)
       // Update now in case we blocked on sysmonnote or spent a long time
       // blocked on schedlock or sysmonlock above.
       now = nanotime()

       // trigger libc interceptors if needed
       if *cgo_yield != nil {...}
       // poll network if not polled for more than 10ms
       lastpoll := int64(atomic.Load64(&sched.lastpoll))
       if netpollinited() && lastpoll != 0 && lastpoll+10*1000*1000 < now {
          atomic.Cas64(&sched.lastpoll, uint64(lastpoll), uint64(now))
          list := netpoll(0) // non-blocking - returns list of goroutines
          if !list.empty() {
             incidlelocked(-1)
             injectglist(&list)
             incidlelocked(1)
          }
       }
       if GOOS == "netbsd" && needSysmonWorkaround {...}
       if scavenger.sysmonWake.Load() != 0 {
          // Kick the scavenger awake if someone requested it.
          scavenger.wake()
       }
       // retake P's blocked in syscalls
       // and preempt long running G's
       if retake(now) != 0 {
          idle = 0
       } else {
          idle++
       }
       // check if we need to force a GC
       if t := (gcTrigger{kind: gcTriggerTime, now: now}); t.test() && atomic.Load(&forcegc.idle) != 0 {
          lock(&forcegc.lock)
          forcegc.idle = 0
          var list gList
          list.push(forcegc.g)
          injectglist(&list)
          unlock(&forcegc.lock)
       }
       if debug.schedtrace > 0 && lasttrace+int64(debug.schedtrace)*1000000 <= now {
          lasttrace = now
          schedtrace(debug.scheddetail > 0)
       }
       unlock(&sched.sysmonlock)
    }
}

retake的操作也相对比较好理解。在p的介绍中我们提到过schedtick、syscalltick、sysmontick三个字段,其作用正是为了sysmon的检查。

sysmontick表示sysmon观测到的调度和系统调用情况,schedtick、syscalltick为实际的调度和系统调用情况。因为sysmon会经常睡眠,所以两者之间会有差异。

  • sysmon在检查所有p的过程中,如果发现sysmontick落后于实际情况,就会以实际情况为准更新sysmontick,同时也不会再做校验。因为sysmon睡眠最大时间为10ms,说明对应的p在10ms内做了调度。
  • 如果sysmontick和实际情况一只,则要看p是否运行一个goroutine超过10ms,如果是,则对m发送信号,触发异步抢占调度;如果p处于syscall状态超过10ms,则将p的持有权释放执行其他g。
func retake(now int64) uint32 {
    n := 0
    // Prevent allp slice changes. This lock will be completely
    // uncontended unless we're already stopping the world.
    lock(&allpLock)
    // We can't use a range loop over allp because we may
    // temporarily drop the allpLock. Hence, we need to re-fetch
    // allp each time around the loop.
    for i := 0; i < len(allp); i++ {
       _p_ := allp[i]
       if _p_ == nil {
          // This can happen if procresize has grown
          // allp but not yet created new Ps.
          continue
       }
       pd := &_p_.sysmontick
       s := _p_.status
       sysretake := false
       if s == _Prunning || s == _Psyscall {
          // Preempt G if it's running for too long.
          t := int64(_p_.schedtick)
          if int64(pd.schedtick) != t {
             pd.schedtick = uint32(t)
             pd.schedwhen = now
          } else if pd.schedwhen+forcePreemptNS <= now {
             preemptone(_p_)
             // 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).
          t := int64(_p_.syscalltick)
          if !sysretake && int64(pd.syscalltick) != t {
             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.
          if runqempty(_p_) && atomic.Load(&sched.nmspinning)+atomic.Load(&sched.npidle) > 0 && pd.syscallwhen+10*1000*1000 > now {
             continue
          }
          // Drop allpLock so we can take sched.lock.
          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(&_p_.status, s, _Pidle) {
             if trace.enabled {
                traceGoSysBlock(_p_)
                traceProcStop(_p_)
             }
             n++
             _p_.syscalltick++
             handoffp(_p_)
          }
          incidlelocked(1)
          lock(&allpLock)
       }
    }
    unlock(&allpLock)
    return uint32(n)
}

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

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

高云FPGA系列教程(10):letter-shell串口终端移植

文章目录 1. letter-shell简介2. letter-shell源码获取3. letter-shell移植4. 函数和变量应用示例本文是高云FPGA系列教程的第10篇文章。 shell,中文是外壳的意思,就是操作系统的外壳。通过shell命令可以操作和控制操作系统,比如Linux中的Shell命令就包括ls、cd、pwd等等。总…

leetcode100----双指针

283. 移动零 给定一个数组 nums&#xff0c;编写一个函数将所有 0 移动到数组的末尾&#xff0c;同时保持非零元素的相对顺序。 请注意 &#xff0c;必须在不复制数组的情况下原地对数组进行操作。 示例 1:输入: nums [0,1,0,3,12] 输出: [1,3,12,0,0] 示例 2:输入: nums …

英飞凌TC3xx--深度手撕HSM安全启动(六)--安全启动的TARA、HARA分析

在之前我们讲解基于Tricore的安全启动流程,但是是不是这种流程就是安全可靠的呢?不确定,因此对启动流程基于信息安全的TARA分析基和于功能安全的HARA分析必不可少。 1、安全启动的TARA分析 首先我们来看看什么叫做TARA分析。 在ISO\SAE 21434 中对于TARA描述为threat analy…

浅析工具dirpro v1.2源码

文章目录 前言源码分析dirpro.pystart.pybackup.pyrely.pyresults.pyend.py 前言 工具简介 dirpro 是一款由 python 编写的目录扫描器专业版&#xff0c;操作简单&#xff0c;功能强大&#xff0c;高度自动化 自动根据返回状态码和返回长度&#xff0c;对扫描结果进行二次整理…

【动手学深度学习-Pytorch版】注意力汇聚:Nadaraya-Watson 核回归

注意力机制中的查询、键、值 在注意力机制的框架中包含了键、值与查询三个主要的部分&#xff0c;其中键与查询构成了注意力汇聚&#xff08;有的也叫作注意力池化&#xff09;。 键是指一些非意识的线索&#xff0c;例如在序列到序列的学习中&#xff0c;特别是机器翻译&…

基于Python+Django的热门旅游景点数据分析系统的设计与实现(源码+lw+部署文档+讲解等)

前言 &#x1f497;博主介绍&#xff1a;✌全网粉丝10W,CSDN特邀作者、博客专家、CSDN新星计划导师、全栈领域优质创作者&#xff0c;博客之星、掘金/华为云/阿里云/InfoQ等平台优质作者、专注于Java、小程序技术领域和毕业项目实战✌&#x1f497; &#x1f447;&#x1f3fb;…

JAVA:实现Excel和PDF上下标

1、简介 最近项目需要实现26个小写字母的上下标功能,自己去网上找了所有Unicode的上下标形式,缺少一些关键字母,顾后面考虑自己创建上下标字体样式,以此来记录。 2、Excel Excel本身是支持上下标,我们可以通过Excel单元格的样式来设置当前字体上下标,因使用的是POI的m…

YOLOv5:修改backbone为ACMIX

YOLOv5&#xff1a;修改backbone为ACMIX 前言前提条件相关介绍ACMIXYOLOv5修改backbone为ACMIX修改common.py修改yolo.py修改yolov5.yaml配置 参考 前言 记录在YOLOv5修改backbone操作&#xff0c;方便自己查阅。由于本人水平有限&#xff0c;难免出现错漏&#xff0c;敬请批评…

【软件设计师-从小白到大牛】上午题基础篇:第四章 法律法规与知识产权

文章目录 前言章节提要一、保护期限真题链接 二、知识产权人确定真题链接 三、侵权判定真题链接 四、标准化基础知识 前言 ​ 本系列文章为观看b站视频以及b站up主zst_2001系列视频所做的笔记&#xff0c;感谢相关博主的分享。如有侵权&#xff0c;立即删除。 视频链接&#xf…

Qt5开发及实例V2.0-第二十三章-Qt-多功能文档查看器实例

Qt5开发及实例V2.0-第二十三章-Qt-多功能文档查看器实例 第23章 多功能文档查看器实例23.1. 简介23.2. 界面与程序框架设计23.2.1. 图片资源23.2.2. 网页资源23.2.3. 测试用文件 23.3 主程序代码框架23.4 浏览网页功能实现23.4.1 实现HtmIHandler处理器 23.5. 部分代码实现23.5…

git 本地分支基础操作

&#xff08;1&#xff09;建立分支 a:基于某个commit建立分支 然后切换 git branch test_branch 6435675ad32c035ed4d9cb6c351de5cbaecddd99 git checkout test_branchb: git checkout 建立分支然后切换 git checkout -b checkout_branchc:分支建立 然后切换 git branch …

【Amazon】AI 代码生成器—Amazon CodeWhisperer初体验 | 开启开挂编程之旅

使用 AI 编码配套应用程序更快、更安全地构建应用程序 文章目录 1.1 Amazon CodeWhisperper简介1.2 Amazon CodeWhisperer 定价2.1 打开VS Code2.2 安装AWS ToolKit插件 一、前言 1.1 Amazon CodeWhisperper简介 1️⃣更快地完成更多工作 CodeWhisperer 经过数十亿行代码的训…

网络分层模型和常见协议介绍

文章目录 网络分层模型和常见协议介绍网络分层模型介绍常见各层协议介绍 网络分层模型和常见协议介绍 理解性记忆&#xff1a;这是我自己创造的一个理解性记忆口诀&#xff0c;大家别笑我&#x1f604; 七层&#xff1a;因为七层协议并没有得到应用&#xff0c;所以物&#xff…

【算法】相向双指针

&#x1f49d;&#x1f49d;&#x1f49d;欢迎来到我的博客&#xff0c;很高兴能够在这里和您见面&#xff01;希望您在这里可以感受到一份轻松愉快的氛围&#xff0c;不仅可以获得有趣的内容和知识&#xff0c;也可以畅所欲言、分享您的想法和见解。 推荐:kuan 的首页,持续学…

【GO】LGTM_Grafana_gozero_配置trace(4)_代码实操及追踪

最近在尝试用 LGTM 来实现 Go 微服务的可观测性&#xff0c;就顺便整理一下文档。 Tempo 会分为 4 篇文章&#xff1a; Tempo 的架构官网测试实操跑通gin 框架发送 trace 数据到 tempogo-zero 微服务框架发送数据到 tempo 本文就是写一下如何在 go-zero 微服务框架里面配置 t…

套接字socket编程的基础知识点

目录 前言&#xff08;必读&#xff09; 网络字节序 网络中的大小端问题 为什么网络字节序采用的是大端而不是小端&#xff1f; 网络字节序与主机字节序之间的转换 字符串IP和整数IP 整数IP存在的意义 字符串IP和整数IP相互转换的方式 inet_addr函数&#xff08;会自…

83、SpringBoot --- 下载和安装 MSYS2、 Redis

★ 下载和安装MSYS2&#xff08;作用&#xff1a;可在Windows模拟一个Linux的编译环境&#xff09; 得到Redis的编译环境——在Linux平台上&#xff0c;这一步可以省略。&#xff08;1&#xff09;登录MSYS2官网&#xff08;http://repo.msys2.org/distrib/ &#xff09;下载M…

前端新轮子Nue,号称替代Vue、React和Svelte

新的简约前端开发工具集Nue.js 于周三发布。在 Hacker News 上介绍它时&#xff0c;前端开发者和Nue.js 的创作者Tero Piirainen表示&#xff0c;它是 React、Vue、Next.js、Vite、Svelte 和 Astro 的替代品。他在 Nue.js的 FAQ 中进一步解释说&#xff0c;它是为网站和响应式用…

【Vue.js】使用Element搭建登入注册界面axios中GET请求与POST请求跨域问题

一&#xff0c;ElementUI是什么&#xff1f; Element UI 是一个基于 Vue.js 的桌面端组件库&#xff0c;它提供了一套丰富的 UI 组件&#xff0c;用于构建用户界面。Element UI 的目标是提供简洁、易用、美观的组件&#xff0c;同时保持灵活性和可定制性 二&#xff0c;Element…

Spring学习笔记6 Bean的实例化方式

Spring学习笔记5 GoF之工厂模式_biubiubiu0706的博客-CSDN博客 Spring为Bean提供了多种实例化方式,通常包括4中(目的:更加灵活) 1.通过构造方法实例化 2.通过简单工厂模式实例化 3.通过factory-bean实例化 4.通过FactoryBean接口实例化 新建模块 spring-005 依赖 <!--S…