Loading [MathJax]/jax/output/CommonHTML/config.js
前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
圈层
工具
发布
首页
学习
活动
专区
圈层
工具
MCP广场
社区首页 >专栏 >你觉得 Golang 在什么时候会抢占 P?

你觉得 Golang 在什么时候会抢占 P?

原创
作者头像
golang开发者
发布于 2024-10-28 08:23:41
发布于 2024-10-28 08:23:41
1150
举报
文章被收录于专栏:go开发者go开发者

在 Go 语言中,Goroutine 是并发模型的核心,而 P(Processor) 是 Go 调度器中的一个关键抽象。理解 Goroutine 调度模型 中的 G(Goroutine)M(Machine,内核线程)P(Processor,逻辑处理器) 的关系可以帮助我们理解 Go 的抢占式调度策略。

Go 调度器使用 G-M-P 模型

  • Goroutine (G):一个 Goroutine 代表一个 Go 协程。
  • Processor (P)P 是逻辑处理器,负责调度和管理 Goroutine,最多有 GOMAXPROCSP。每个 P 可以运行一个 Goroutine
  • Machine (M)M 是操作系统的内核线程。每个 M 需要绑定一个 P 来执行 Goroutine

Go 中的抢占式调度

Go 的调度器采用的是协作式调度为主,抢占式调度为辅。协作式调度意味着 Goroutine 需要主动放弃控制权来让其他 Goroutine 运行,比如调用系统调用或者 Goroutine 自己调用 runtime.Gosched()

抢占式调度则是为了防止某些 Goroutine 占用 CPU 太久(比如某个 Goroutine 在长时间执行计算密集型任务),Go 1.14 引入了针对 计算密集型 Goroutine抢占式调度。抢占式调度可以在以下场景下触发:

  • Goroutine 执行时间过长,特别是没有主动进行系统调用、调度让出等行为时。
  • Goroutine 执行在较长的函数调用链上,或者在一些函数的栈帧扩展时(例如深度递归调用或大数组操作时)。
抢占 P 的时机
  1. 系统调用 (syscall) 后:当 Goroutine 执行系统调用后,Goroutine 会让出 P,此时调度器可能会选择调度其他的 Goroutine 来运行。
  2. 垃圾回收 (GC) 阶段:当触发垃圾回收时,调度器会在合适时机抢占 Goroutine,确保 GC 可以进行。
  3. 计算密集型任务被长时间运行:从 Go 1.14 开始,调度器会定期检查长时间运行的 Goroutine,并进行抢占。

代码示例:抢占式调度与长时间运行的 Goroutine

下面的例子展示了一个 Goroutine 在执行计算密集型任务时如何可能会被 Go 的抢占式调度机制打断。

代码语言:go
AI代码解释
复制
package main

import (
	"fmt"
	"runtime"
	"time"
)

// 模拟一个计算密集型任务
func busyLoop() {
	for i := 0; i < 1e10; i++ {
		// 占用 CPU,但没有主动让出调度权
	}
	fmt.Println("Finished busy loop")
}

func main() {
	runtime.GOMAXPROCS(1) // 设置只有 1 个 P

	go func() {
		for {
			fmt.Println("Running another goroutine...")
			time.Sleep(500 * time.Millisecond) // 每 500 毫秒休息一次
		}
	}()

	busyLoop() // 执行计算密集型任务

	time.Sleep(2 * time.Second)
}
代码解析:
  1. runtime.GOMAXPROCS(1):我们将 GOMAXPROCS 设置为 1,意味着整个程序中只有一个 P,这样所有 Goroutine 都只能在这个 P 上调度。
  2. busyLoop:这是一个计算密集型任务,在没有主动进行系统调用或让出调度权的情况下,循环执行大量的操作,耗尽 CPU 时间。
  3. 抢占:虽然 busyLoop 没有主动让出 CPU,但由于 Go 的抢占式调度机制,调度器可能会在合适的时间点打断 busyLoop,让其他 Goroutine(比如打印 "Running another goroutine..." 的那个 Goroutine)得到执行机会。
输出示例:
代码语言:bash
AI代码解释
复制
Running another goroutine...
Running another goroutine...
...
Finished busy loop

我们可以看到,尽管 busyLoop 是一个计算密集型任务,其他的 Goroutine 仍然会间歇性地被调度并执行。这个就是 Go 抢占式调度的效果。

抢占的实现机制

抢占式调度的核心机制是 定期检查 Goroutine 的执行时间。Go 调度器在后台维护一个时间戳,记录 Goroutine 上次被调度的时间。调度器每隔一段时间会检查当前运行的 Goroutine,如果 Goroutine 占用了 CPU 超过一定时间,调度器就会标记这个 Goroutine 需要被抢占,然后调度其他的 Goroutine 来执行。

抢占式调度通过以下方式触发:

  1. 函数调用边界:当 Goroutine 进行函数调用时,Go runtime 会在合适的时机插入抢占检查点。
  2. 栈增长:当 Goroutine 的栈增长(如递归调用导致栈内存增长)时,调度器也会插入抢占检查。
  3. GC 安全点:垃圾回收过程中,调度器也会尝试抢占。

通过代码观察抢占效果

我们可以通过使用 GODEBUG 环境变量,启用抢占式调度的调试日志,观察抢占调度的具体行为。运行如下代码时,启用调试模式:

代码语言:bash
AI代码解释
复制
GODEBUG=schedtrace=1000,scheddetail=1 go run main.go
  • schedtrace=1000 表示每隔 1000 毫秒输出一次调度器状态。
  • scheddetail=1 表示输出详细的调度器信息。
输出内容解释

在输出的调试信息中,我们可以看到调度器何时抢占了 Goroutine,何时让出了 P,以及具体的调度行为。调试信息会包括如下内容:

  • idle M:表示某个 M(线程)变成空闲状态。
  • new work:表示调度器找到了新的工作,分配给 P
  • steal work:表示调度器从其他 P 中窃取任务来运行。

最后我们来总结一下

  • Go 的调度器主要基于 协作式调度,但是对于计算密集型任务会通过 抢占式调度 机制防止长时间占用 CPU。
  • 抢占调度在计算密集型 Goroutine、系统调用后、垃圾回收等场景下被触发。
  • Go 1.14 引入了针对长时间运行的 Goroutine 的抢占式调度,使得 Goroutine 不会因为计算密集任务长时间阻塞 CPU。

这使得 Go 语言能更加高效地运行并发程序,避免单个 Goroutine 长时间霸占 CPU,影响其他 Goroutine 的执行。

原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。

如有侵权,请联系 cloudcommunity@tencent.com 删除。

原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。

如有侵权,请联系 cloudcommunity@tencent.com 删除。

评论
登录后参与评论
暂无评论
推荐阅读
编辑精选文章
换一批
从源码剖析Go语言基于信号抢占式调度
在 Go 的 1.14 版本之前抢占试调度都是基于协作的,需要自己主动的让出执行,但是这样是无法处理一些无法被抢占的边缘情况。例如:for 循环或者垃圾回收长时间占用线程,这些问题中的一部分直到 1.14 才被基于信号的抢占式调度解决。
luozhiyun
2021/03/29
1.2K0
GoLang协程Goroutiney原理与GMP模型详解
Goroutine是Go语言中的一种轻量级线程,也成为协程,由Go运行时管理。它是Go语言并发编程的核心概念之一。Goroutine的设计使得在Go中实现并发编程变得非常简单和高效。
张飞的猪
2024/11/09
1120
GoLang协程Goroutiney原理与GMP模型详解
Golang/Go goroutine调度器原理/实现
Go语言在2016年再次拿下TIBOE年度编程语言称号,这充分证明了Go语言这几年在全世界范围内的受欢迎程度。如果要对世界范围内的gopher发起一次“你究竟喜欢Go的哪一点”的调查,我相信很多Gopher会提到:goroutine。
sunsky
2020/08/20
1.1K0
Golang 语言的 goroutine 调度器模型 GPM
Golang 语言与其他编程语言之间比较,最大的亮点就是 goroutine,使 Golang 语言天生支持并发,可以高效使用 CPU 的多个核心,而并发执行需要一个调度器来协调。
frank.
2021/01/22
1.3K0
golang 重要知识:golang 调度
Go 的调度机制相当于我们微服务里的基础组件。很多运行时操作都涉及到了调度的关联。本文会细聊调度概念,策略,以及它的机制。当然,也少不了最常提及的 GMP 模型。
lincoln
2021/07/30
1.1K1
Go 运行时面试题
在 Go 语言中,goroutine 是一种非常轻量级的执行线程。goroutine 是 Go 语言并发模型的核心,允许同时执行多个函数调用。goroutines 在 Go 运行时环境中被多路复用到少量的操作系统(OS)线程上,以实现高效并发。
Lemon黄
2023/12/13
4000
Go 运行时面试题
Golang 协程 与 Java 线程池的联系
如何理解Golang的协程,我觉得可以用一句话概括: Golang 提供的协程是一种支持任务分时复用的高级线程池实现。
大忽悠爱学习
2023/10/19
4260
Golang 协程 与 Java 线程池的联系
Go调度器系列(2)宏观看调度器
上一篇文章《Go语言高阶:调度器系列(1)起源》,学goroutine调度器之前的一些背景知识,这篇文章则是为了对调度器有个宏观的认识,从宏观的3个角度,去看待和理解调度器是什么样子的,但仍然不涉及具体的调度原理。
大彬
2019/04/11
6490
Go调度器系列(2)宏观看调度器
如何定位 golang 进程 hang 死的 bug
之前在 golang 群里有人问过为什么程序会莫名其妙的 hang 死然后不再响应任何请求。单核 cpu 打满。
sunsky
2020/08/20
2K0
Go 调度器 M, P 和 G
网上已经有很多关于Go调度器的文章了,多看一些,可以加深记忆,也可以对比查看文章中是否有不准确的地方,更全面的了解Go的调度器。
公众号-利志分享
2022/04/25
2360
Go 调度器 M, P 和 G
Go 1.14 正式发布,重要更新有哪些
Go 在 2019 年发布了Go 1.12与Go 1.13。Go 1.13 的大部分变化在于工具链、运行时和库的实现。时隔半年,Go 1.14 正式发布。
aoho求索
2020/03/18
1.2K0
Go 1.14 正式发布,重要更新有哪些
深度解密Go语言之scheduler
一个月前,《Go 语言高级编程》作者柴树杉老师在 CSDN 上发表了一篇《Go 语言十年而立,Go2 蓄势待发》,视角十分宏大。我们既要低头看路,有时也要抬头看天,这篇文章就属于“抬头”看天类的,推荐阅读。
梦醒人间
2019/09/04
1.1K0
深度解密Go语言之scheduler
了解go在协程调度上的改进
协作式调度是指以多个任务之间以协作的方式切换执行,每个任务执行一会,任务执行到某个点时会自己让出当前资源交给其他正在等待的任务,这显得比较主动和自愿。
秋名山白又白
2022/01/23
1.4K0
了解go在协程调度上的改进
Go 协作与抢占
我们在分析调度循环[1]的时候总结过一个问题:如果某个 G 执行时间过长,其他的 G 如何才能被正常地调度?这便涉及到有关调度的两个理念:协作式调度与抢占式调度。
梦醒人间
2020/04/26
2.2K0
深入浅出Go调度器中的GMP模型
今天给大家介绍一下Go协程调度器的G-M-P的模型,以及一个线程在该模型下是如何被调度的。
Go学堂
2023/08/28
1.1K0
深入浅出Go调度器中的GMP模型
浅谈:Golang 并发
在面向进程设计的系统中,进程(process)是程序的基本执行实体;在当代面向线程设计的计算机结构中,进程是线程的容器。 进程是程序(指令和数据)的真正运行实例。用户下达运行程序的命令后,就会产生进程。同一程序可产生多个进程(一对多关系),以允许同时有多位用户运行同一程序,却不会相冲突。
机械视角
2019/10/23
7600
浅谈:Golang 并发
抢占系统调用执行时间过长的goroutine(22)
本文是《Go语言调度器源代码情景分析》系列的第22篇,也是第六章《抢占调度》的第2小节。
阿波张
2019/06/24
1.5K0
Go1.14发布了,快来围观新的特性啦
如期而至,Go1.14发布了,和往常一样,该版本保留了Go 1兼容性的承诺,这个版本的大部分更新在工具链 、运行时库的性能提升方面,总的来说,还是在已有的基础上不断优化提成,大家期待的泛型还没有到来,下面一块看看新的变化吧,以下变化我本地测试过。
阿伟
2020/03/10
5980
Go1.14发布了,快来围观新的特性啦
Golang GPM 模型剖析
Golang 程序启动时首先会创建进程,然后创建主线程,主线程会执行 runtime 初始化的一些代码,包括调度器的初始化,然后会启动调度器,调度器会不断寻找需要运行的 goroutine 与内核线程绑定运行。
田飞雨
2021/12/13
1.2K0
Golang GPM 模型剖析
深入理解Go调度原理和实现
本文深入分析Go调度原理和实现,全文包含的主要内容有:Go程序是怎么运行起来的,经历了哪些流程,调度G的策略和时机,程序是如何在执行runtime代码与用户代码之间来回切换的。文章内容很长,感兴趣的同学可以收藏慢慢看。本文中分析的代码是Go1.14版本,涉及到的文件都在runtime包下。
数据小冰
2022/08/15
1.1K0
深入理解Go调度原理和实现
相关推荐
从源码剖析Go语言基于信号抢占式调度
更多 >
领券
💥开发者 MCP广场重磅上线!
精选全网热门MCP server,让你的AI更好用 🚀
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档