Go并发调度进阶-循环调度,不是你理解的死循环

go gmp ---goroutine回收及调度循环 经过前面的知识,我们已经知道一个m启动后会调用schedule进行一轮调度,那经过这一轮调度后这个m还会再进行下一轮调度么?答案是的,那我们来看一下schedule是怎么做到循环调度的。 在分析goroutine创建代码newproc时我们已经知道,一个gouroutine执行完后会调用goexit,那我们来看一下是不是goexit实现了循环调度的功能呢?先看代码 //asm_amd64.s TEXT runtime·goexit(SB),NOSPLIT,$0-0 BYTE $0x90 // NOP C 阅读详情

图片

Go并发调度进阶-【公粽号:堆栈future】

原文
3. 循环调度

所有的GMP初始化工作都已经完成了,是时候启动运行时调度器了。我们已经知道,当所有准备工作都完成后, 最后一个开始执行的调用就是runtime.mstart了。

mstart主要功能:

  • 确定执行栈的边界

  • 启动mstart1

  • 设置退出线程标记osStack=true

  • 调用mexit(osStack)退出线程

再来看下mstart1:

  • 如果当前m并非m0,那么要求绑定p

  • 开始调用schedule()开始调度

所以我们可以看到调度循环schedule无法返回,因此最后一个mexit目前还不会被执行,因此当下所有的Go程序创建的线程都无法被释放。

1. M与P的绑定

M 与 P 的绑定过程(acquirep函数调用wirep绑定)只是简单的将 P 链表中的 P ,保存到 M 中的 P 指针上。绑定前,P 的状态一定是 _Pidle,绑定后 P 的状态一定为 _Prunning。

 // 将 p 绑定到 m,p 和 m 互相引用
 _g_.m.p.set(_p_) // *_g_.m.p = _p_
 _p_.m.set(_g_.m) // *_p_.m = _g_.m

 // 修改 p 的状态
 _p_.status = _Prunning

2. M的park和unpark

无论出于什么原因,当 M 需要被park时,可能会执行stopm调用。此调用会将 M 进行park,并阻塞到它被unpark时。这一过程就是工作线程的暂止(park)和复始(unpark)。

func stopm() {
 _g_ := getg()
 (...)

 // 将 m 放回到 空闲列表中,因为我们马上就要park了
 lock(&sched.lock)
 mput(_g_.m)
 unlock(&sched.lock)

 // park当前的 M,在此阻塞,直到被唤醒
 notesleep(&_g_.m.park)

 // 清除暂止的 note
 noteclear(&_g_.m.park)

 // 此时已经被unpark,说明有任务要执行
 // 立即 acquire P
 acquirep(_g_.m.nextp.ptr())
 _g_.m.nextp = 0
}

它的流程也非常简单,将 m 放回至空闲列表中,而后使用 note 注册一个暂止通知, 阻塞到它重新被复始。

3. 核心调度schedule

// 调度器的一轮:找到 runnable Goroutine 并进行执行且永不返回
func schedule() {
 _g_ := getg()
 (...)
 top:
   if sched.gcwaiting != 0 {
    // 如果需要 GC,不再进行调度
    gcstopm() //park 还有就是偷取没有的时候也会park
    goto top
   }
   var gp *g
   (...)
   if gp == nil {
   // 说明不在 GC
   // 每调度 61 次,就检查一次全局队列,保证公平性
   // 否则两个 Goroutine 可以通过互相 respawn 一直占领本地的 runqueue
   if _g_.m.p.ptr().schedtick%61 == 0 && sched.runqsize > 0 {
    lock(&sched.lock)
    // 从全局队列中偷 g
    gp = globrunqget(_g_.m.p.ptr(), 1)
    unlock(&sched.lock)
  }
 }
  
   if gp == nil {
   // 说明不在 gc
   // 两种情况:
   //  1. 普通取
   //  2. 全局队列中偷不到的取
   // 从本地队列中取
   gp, inheritTime = runqget(_g_.m.p.ptr())
   (...)
 }
  if gp == nil {
   // 如果偷都偷不到,则休眠,在此阻塞
   gp, inheritTime = findrunnable()
  }

  // 这个时候一定取到 g 了

  if _g_.m.spinning {
   // 如果 m 是自旋状态,则
   //   1. 从自旋到非自旋
   //   2. 在没有自旋状态的 m 的情况下,再多创建一个新的自旋状态的 m
   resetspinning()
  }
   
  if gp.lockedm != 0 {
     // 如果 g 需要 lock 到 m 上,则会将当前的 p
     // 给这个要 lock 的 g
     // 然后阻塞等待一个新的 p
     startlockedm(gp)
     goto top
  }

  // 开始执行
  execute(gp, inheritTime)
}

看下execute:

func execute(gp *g, inheritTime bool) {
 _g_ := getg()

 // 将 g 正式切换为 _Grunning 状态
 casgstatus(gp, _Grunnable, _Grunning)
  
 // M 与 G 绑定开始执行
 _g_.m.curg = gp
 gp.m = _g_.m

 // G终于开始执行了
 gogo(&gp.sched)
}

当开始执行 execute 后,g 会被切换到 _Grunning 状态,同时将 m 和 g 进行绑定。最终调用 gogo 开始执行。

gogo的执行是一段汇编代码,晦涩难懂哈。但是大致意思就是完成g0到gp的栈切换,然后开始执行runtime.main函数或者用户自定义的goroutine任务。

执行完成后,main goroutine 直接调用 eixt(0) 退出,普通 goroutine 则调用 goexit -> goexit1 -> mcall,完成普通 goroutine 退出后的清理工作,然后切换到 g0 栈,调用 goexit0 函数,将普通 goroutine 添加到缓存池中,再调用 schedule 函数进行新一轮的调度。

调度太复杂了,大致流程如下:

schedule() -> execute() -> gogo() -> goroutine 任务 -> goexit() -> goexit1() -> mcall() -> goexit0() -> schedule()

可以看出,一轮调度从调用 schedule 函数开始,经过一系列过程再次调用 schedule 函数来进行新一轮的调度,从一轮调度到新一轮调度的过程称之为一个调度循环。

这里说的调度循环是指某一个工作线程的调度循环,而同一个Go 程序中存在多个工作线程,每个工作线程都在进行着自己的调度循环。

4. 小结

由于调度核心太过于复杂,大家只需要了解大致流程或者思路就OK,没必要深入底层的细节。因为陷得越深你就越难理解一些细枝末节,我写到这里放弃了,不想深入研究下去了,只需要知道这个循环调度不是一个死循环就行了,而且调度链中的各个环节也应该大致了解下。好了今天就先到这里,欢迎大家关注转发分享哈。

公粽号:堆栈future

使很多处于迷茫阶段的coder能从这里找到光明,堆栈创世,功在当代,利在千秋

GO 遇到循环调用怎么办 当项目越来越大,难免会遇到两个包相互调用的问题,先不提通过重构文件目录组织结构来避免这种情况,如果项目架构上大方向没有问题,尤其是项目排期紧张时,这时候我们就可以采用一种优雅的方式来绕过循环调用 比如 pkgA包中定义了方法fa1,其中pkgA中的fa1中调用pkgB包中的fb2, import object/pkgB func fa1(){ pkgB.fb2() //调用pkgB包中的fb2方法 } pkgB包中定义方法fb1,fb2,而pkgB包中的fb1调用pkgA包中的fa1 import ob 阅读详情

相关推荐

【golang】goroutine调度的坑

【golang】goroutine调度的坑 今天说说我遇到的一个小坑, 关于goroutine 调度的问题。 关于goroutine的调度,网上资料已经一大堆了,这里就不再赘述了。还是简单的说一下我理解的goroutine的调度。goroutine是语言层面的,它和内核线程是M:N的关系,并且用了分段栈,是相当轻量了。如果设置runtime.GOMAXPROCS为1,那么会有一个上下文G,在G...

小辣抓 1801

Go语言的运行机制&程序是怎么跑起来的

学习Golang有一段时间了,自己看着各种教程也码了些demo。其实接触了这么多语言,当因为工作、项目、兴趣所驱在短时间切换一门编程语言时,并不会太难上手,甚至会对了解一些很雷同的基础语法感到枯燥,但这是必经之路。对于一个技术爱好者而言,技术广度、技术深度、技术新特性等往往是最好的兴奋剂。今天这篇文章主要结合最近的资料学习,对Go语言的运行机制及Go程序的运作进行一些稍微深入的分析及总结,对Go的启动和执行流程建立简单的宏观认知~ 为什么Go语言适合现代的后端编程环境? 服务类应用以API居多,IO密.

AlexGeek 2943

GO语言协程调度原理和使用方法

GO语言协程调度

geegtb的博客 381

GO基础---for循环

GO语言 for循环总结

u010950480的博客 1198

CentOS 7下glibc升级到2.28的保姆级避坑指南(含GCC 7.3.1编译配置)

本文详细介绍了在CentOS 7系统中将glibc升级到2.28版本的完整流程,包括GCC 7.3.1的编译配置、环境变量设置、glibc编译的细节处理以及生产环境验证。文章特别强调了风险控制和避坑技巧,适用于需要提升系统兼容性的企业级用户。

weixin_29611239的博客 408

Rockchip平台Android相机HAL3开发实战:从配置到调试全流程解析

本文深入解析了Rockchip平台Android相机HAL3的开发全流程。从理解AAL与PSL核心架构、解析关键配置文件(如camera3_profiles.xml和Tuning文件),到搭建编译环境、进行增量部署,并提供了系统日志分析、V4L2调试等高级问题排查技巧,为开发者提供了一套从配置到调试的实战指南。

docker7nomad的博客 843

Go语言的协程中,写死循环的注意点

先来看一段代码: type Test struct { Name string } func NewTest(n string) *Test { return &Test{ Name: n, } } func (t *Test) Start() { go func() { for { print(t.Name) time.Sleep(1*time.Second) } }() } func T1() { t1:=NewTest("t1 ") t1.Start

m0_46455711的博客 1965

Java 开发打飞机小游戏(附完整源码)

上图 写在前面 技术源于分享,所以今天抽空把自己之前用java做过的小游戏整理贴出来给大家参考学习。java确实不适合写桌面应用,这里只是通过这个游戏让大家理解oop面向对象编程的过程,纯属娱乐。代码写的很简单,也很容易理解,并且注释写的很清楚了,还有问题,自己私下去补课学习。 完整代码 敌飞机 import java.util.Random; 敌飞机: 是飞行物,也是敌人 public class Airplane extends FlyingObject implements Enemy {

一只独孤的程序猿 1万+

再见 Go 面试官:单核 CPU,开两个 Goroutine,其中一个死循环,会怎么样?

大家好,我是煎鱼。最近金三银四,是面试的季节。在我的 Go 读者交流群里出现了许多小伙伴在讨论自己面试过程中所遇到的一些 Go 面试题。若大家有兴趣,欢迎加我的微信进群一同交流。今天的男主...

EDDYCJY的博客 1755

七步设计高频变压器

七步设计高频变压器

m0_71394959的博客 5400

SPEI计算避坑指南:gma中Distribution和FitMethod参数怎么选?

本文深入探讨了在SPEI计算中如何科学选择gma库中的Distribution和FitMethod参数,帮助研究者避开常见陷阱。详细分析了LogLogistic、Gamma和Pearson3三种分布的适用场景,以及LMoment与MLE拟合方法的优缺点,为气象干旱研究提供实用指南。

weixin_30308101的博客 250

理解golang调度之二 :Go调度器

简介 第一篇文章解释了关于操作系统层级的调度,我认为这对于理解Go的调度是很重要的。这一部分我会在语义层级解释Go调度器是如何工作的,并且着重关注它的一些高级行为。Go 调度器是一个十分复杂的系统,细节不重要,重要的是对于其工作和行为有一个好的理解,这会让你做出更好的工程方面的决定。 从一个程序开始 当你的go程序启动,主机上定义的每一个虚拟内核都会为它分配一个逻辑处理器(P),如果你的处理...

程序猿的世界 1281

从解压到编译:手把手带你用TeXstudio搞定第一篇ACM LaTeX论文

本文详细指导如何使用TeXstudio从解压ACM LaTeX模板到成功编译第一篇学术论文的全过程。涵盖环境准备、模板配置、TeXstudio设置及常见问题排查,特别针对ACM会议论文格式要求,帮助学术新人快速掌握LaTeX写作技巧。

weixin_30655569的博客 254

深度解密Go语言之基于信号的抢占式调度

不知道大家在实际工作中有没有遇到过老版本 Go 调度器的坑:死循环导致程序“死机”。我去年就遇到过,并且搞出了一起 P0 事故,还写了篇弱智的找 bug 文章。识别事故的本质,并且用一个非...

kevin_tech的博客,微信搜「网管叨bi叨」 739

UNet改进(3):Local Attention增强的UNet网络-医学图像分割

本文提出一种改进的UNet网络,通过引入局部注意力机制增强医学图像分割性能。该方法在传统UNet编码器-解码器结构中,创新性地整合了通道注意力和空间注意力模块,使网络能自适应聚焦关键区域。注意力机制通过特征重标定增强有用信息并抑制噪声,显著提升了小目标分割和边缘检测的精度,同时保持较低计算开销。实验表明,该方法在肿瘤分割等任务中具有更好表现,为医学图像处理提供了新的优化思路。

2501_91516851的博客 2063

YOLO26改进策略【YOLO和Mamba】| 替换骨干 Mamba-YOLO26-T !!! 最新的发文热点

模块是模型中的一个重要组成部分,其主要作用是在模型的初始阶段对输入图像进行处理,方便后续的特征提取和目标检测。以下是对。

Limiiiing的博客 900

【go】详解golang如何实现高效的并发任务?

在实际开发中,理解调度器的运行机制并结合性能优化策略,可以显著提升Go程序的并发能力和资源利用率。模型实现高效的并发任务管理。Go语言的调度器是其并发模型的核心,通过。

wamp0001的博客 946

NTscan扫描工具

ntscan是一个扫描器,可以扫描没有密码和弱密码的共享,先到配置文件中填写猜测的用户名和密码,扫描过程中右侧出现的是扫描日志。

Golang 的 协程调度机制

前序 正确地认识 G , M , P 三者的关系,能够对协程的调度机制有更深入的理解! 本文将会完整介绍完 go 协程的调度机制,包含: 调度对象的主要组成 各对象的关系 与 分工 gorutine 协程是如何被执行的 内核线程 sysmon 对 gorutine 的管理 gorutine 协程中断挂起 与 恢复 GOMAXPROCS 如何影响 go 的并发性能 调度器的三个基本对象: Golang 简称 Go,Go 的协程(goroutine) 和我们常见的线程(Thread)一样,拥有其

hjxisking的专栏 825

汉书HE8,好用的汇编代码编写工具

汉书HE8,好用的汇编代码编写工具早期编写汇编代码所用功能强大

lwip-2.0.3

lwip是瑞典计算机科学院(SICS)的Adam Dunkels 开发的一个小型开源的TCP/IP协议栈。实现的重点是在保持TCP协议主要功能的基础上减少对RAM 的占用。

深度解密 Go 语言之基于信号的抢占式调度

作者 | qcrao 责编 | 欧阳姝黎不知道大家在实际工作中有没有遇到过老版本 Go 调度器的坑:死循环导致程序“死机”。我去年就遇到过,并且搞出了一起 P0 事故,还写了篇...

优快云资讯 1411

SAP B1中文培訓教材_SDK

公司内部培训资料,对开发工具 ,DIAPI, UIAPI,Add-ON有详细解说

理解 goroutine 的并发

线程(Thread)对于语言的重要性不言而喻,每个语言都要“发明”自己最高效的线程库以说明自己的厉害,go 也不能免俗的。但 Go 语言却不认为自己是线程,叫 goroutine? Why? 本文通过案例加深对goroutine 的本质理解,在此基础上介绍 goroutine 编程涉及的内容以及四个编程准则。

pmlpml的专栏 7576

golang的goroutine调度机制

一直对goroutine的调度机制很好奇,最近在看雨痕的golang源码分析,(基于go1.4) 感觉豁然开朗,受益匪浅; 去繁就简,再加上自己的一些理解,整理了一下 ~~ 调度器 主要基于三个基本对象上,G,M,P(定义在源码的src/runtime/runtime.h文件中) 1.G代表一个goroutine对象,每次go调用的时候,都会创建一个G对象 2.M代表一个线程,每次创建一个M的时候,都会有一个底层线程创建;所有的G任务,最终还是在M上执行 3.P代...

zm1_1zm 2451

Go 是如何做抢占式调度的?

先理解什么是“抢占”。一句话版本:Go 通过向线程发送信号,在安全点打断 goroutine,实现异步抢占式调度。时间片到→ 标记抢占→ 发信号→ 跳到调度器→ 切换 goroutine。

牛奔的博客 416
上一篇: Go微服务架构实战 中篇:4. 基于ingress的限流,路径匹配和重写实战
下一篇: Go微服务架构实战 中篇:5. k8s基于ingress和service实现金丝雀发布和蓝绿发布
stackfuture
博客等级 码龄5年 23粉丝 38原创
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

个

红包个数最小为10个

元

红包金额最低5元

当前余额3.43元 前往充值 >
需支付:10.00元
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付元
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值