面试官:谈谈 Go GMP 中的抢占式调度

2023-10-18 10:28

本文主要是介绍面试官:谈谈 Go GMP 中的抢占式调度,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

大家好,我是木川

一、什么是抢占式调度

在 1.2 版本之前,Go 的调度器仍然不支持抢占式调度,程序只能依靠 Goroutine 主动让出 CPU 资源才能触发切换调度,会导致下面的问题:

  • 一旦 Goroutine 出现死循环的代码逻辑,那么 G 将永久占用分配给它的 P 和 M,而位于同一个 P 中的其他 G 将得不到调度,出现“饿死”的情况

  • 当只有一个 P(GOMAXPROCS=1)时,整个 Go 程序中的其他 G 都将“饿死”

为解决上面的问题:

  • Go 1.2 中实现了基于协作的“抢占式”调度

  • Go 1.14 中实现了基于信号的“抢占式”调度

二、基于协作的抢占式调度

协作式:大家都按事先定义好的规则来,比如:一个 Goroutine 执行完后让出 P,然后下一个 Goroutine 被调度到 P 上运行。这样做的缺点就在于是否让出 P 的决定权在 Goroutine 自身。一旦某个 G 不主动让出 P 或执行时间较长,那么后面的 Goroutine 只能等着,没有方法让前者让出 P,导致延迟甚至饿死。

基于协作的抢占式调度流程:

  • 编译器会在调用函数前插入 runtime.morestack,让运行时有机会在这段代码中检查是否需要执行抢占调度

  • Go 语言运行时会在垃圾回收暂停程序、系统监控发现 Goroutine 运行超过 10ms,那么会在这个协程设置一个抢占标记

  • 当发生函数调用时,可能会执行编译器插入的 runtime.morestack,它调用的 runtime.newstack 会检查抢占标记,如果有抢占标记就会触发抢占让出 CPU,切到调度主协程里

这种解决方案只能说局部解决了“饿死”问题,只在有函数调用的地方才能插入“抢占”代码(埋点),对于没有函数调用而是纯算法循环计算的 G,Go 调度器依然无法抢占。

比如,死循环等并没有给编译器插入抢占代码的机会,以下程序在 Go 1.14 之前的 Go 版本中,运行后会一直卡住,而不会打印 I got scheduled!

package mainimport ("fmt""runtime""time"
)func main() {runtime.GOMAXPROCS(1)go func() {for {}}()time.Sleep(time.Second) // 等待 G 创建完成fmt.Println("I got scheduled!")
}

为了解决这些问题,Go 在 1.14 版本中增加了对非协作的抢占式调度的支持,这种抢占式调度是基于系统信号的,也就是通过向线程发送信号的方式来抢占正在运行的 Goroutine

三、基于信号的抢占式调度

非协作式:就是由 Go runtime 来决定一个 Goroutine 运行多长时间,如果你不主动让出,对不起,我有手段可以抢占你,把你踢出去,让后面的 Goroutine 进来运行。

真正的抢占式调度是基于信号完成的,所以也称为“异步抢占”。不管协程有没有意愿主动让出 CPU 运行权,只要某个协程执行时间过长,就会发送信号强行夺取 CPU 运行权。

基于信号的抢占式调度流程:

  • M 注册一个 SIGURG 信号的处理函数:sighandler

  • sysmon 启动后会间隔性的进行监控,最长间隔 10ms,最短间隔 20us。如果发现某协程独占 P 超过 10ms,会给 M 发送抢占信号

  • M 收到信号后,内核执行 sighandler 函数把当前协程的状态从 _Grunning 正在执行改成 _Grunnable 可执行,把抢占的协程放到全局队列里,M 继续寻找其他 Goroutine 来运行

  • 被抢占的 G 再次调度过来执行时,会继续原来的执行流

最后给自己的原创 Go 面试小册打个广告,如果你从事 Go 相关开发,欢迎扫码购买,目前 10 元买断,加下面的微信发送支付截图额外赠送一份自己录制的 Go 面试题讲解视频

f605bea654e05d25cac3489243575531.jpeg

673c62a27011f4d8d1edbcbb058edc51.png

如果对你有帮助,帮我点一下在看或转发,欢迎关注我的公众号

这篇关于面试官:谈谈 Go GMP 中的抢占式调度的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!


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

相关文章

Go中select多路复用的实现示例

《Go中select多路复用的实现示例》Go的select用于多通道通信,实现多路复用,支持随机选择、超时控制及非阻塞操作,建议合理使用以避免协程泄漏和死循环,感兴趣的可以了解一下... 目录一、什么是select基本语法:二、select 使用示例示例1:监听多个通道输入三、select的特性四、使用se

Go语言使用Gin处理路由参数和查询参数

《Go语言使用Gin处理路由参数和查询参数》在WebAPI开发中,处理路由参数(PathParameter)和查询参数(QueryParameter)是非常常见的需求,下面我们就来看看Go语言... 目录一、路由参数 vs 查询参数二、Gin 获取路由参数和查询参数三、示例代码四、运行与测试1. 测试编程路

Go语言使用net/http构建一个RESTful API的示例代码

《Go语言使用net/http构建一个RESTfulAPI的示例代码》Go的标准库net/http提供了构建Web服务所需的强大功能,虽然众多第三方框架(如Gin、Echo)已经封装了很多功能,但... 目录引言一、什么是 RESTful API?二、实战目标:用户信息管理 API三、代码实现1. 用户数据

Go语言网络故障诊断与调试技巧

《Go语言网络故障诊断与调试技巧》在分布式系统和微服务架构的浪潮中,网络编程成为系统性能和可靠性的核心支柱,从高并发的API服务到实时通信应用,网络的稳定性直接影响用户体验,本文面向熟悉Go基本语法和... 目录1. 引言2. Go 语言网络编程的优势与特色2.1 简洁高效的标准库2.2 强大的并发模型2.

深入理解go中interface机制

《深入理解go中interface机制》本文主要介绍了深入理解go中interface机制,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学... 目录前言interface使用类型判断总结前言go的interface是一组method的集合,不

Go语言使用sync.Mutex实现资源加锁

《Go语言使用sync.Mutex实现资源加锁》数据共享是一把双刃剑,Go语言为我们提供了sync.Mutex,一种最基础也是最常用的加锁方式,用于保证在任意时刻只有一个goroutine能访问共享... 目录一、什么是 Mutex二、为什么需要加锁三、实战案例:并发安全的计数器1. 未加锁示例(存在竞态)

Go语言使用select监听多个channel的示例详解

《Go语言使用select监听多个channel的示例详解》本文将聚焦Go并发中的一个强力工具,select,这篇文章将通过实际案例学习如何优雅地监听多个Channel,实现多任务处理、超时控制和非阻... 目录一、前言:为什么要使用select二、实战目标三、案例代码:监听两个任务结果和超时四、运行示例五

go动态限制并发数量的实现示例

《go动态限制并发数量的实现示例》本文主要介绍了Go并发控制方法,通过带缓冲通道和第三方库实现并发数量限制,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面... 目录带有缓冲大小的通道使用第三方库其他控制并发的方法因为go从语言层面支持并发,所以面试百分百会问到

Go语言并发之通知退出机制的实现

《Go语言并发之通知退出机制的实现》本文主要介绍了Go语言并发之通知退出机制的实现,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧... 目录1、通知退出机制1.1 进程/main函数退出1.2 通过channel退出1.3 通过cont

Go语言编译环境设置教程

《Go语言编译环境设置教程》Go语言支持高并发(goroutine)、自动垃圾回收,编译为跨平台二进制文件,云原生兼容且社区活跃,开发便捷,内置测试与vet工具辅助检测错误,依赖模块化管理,提升开发效... 目录Go语言优势下载 Go  配置编译环境配置 GOPROXYIDE 设置(VS Code)一些基本