美文网首页
Go调度相关

Go调度相关

作者: 夜空中乄最亮的星 | 来源:发表于2021-06-10 14:38 被阅读0次

go 调度
go routinue在线程中进行调度

GPM的概念:

  • G(Goroutine): 即Go协程,每个go关键字都会创建一个协程。
  • M(Machine): 工作线程,在Go中称为Machine。
  • P(Processor): 逻辑处理器(Go中定义的一个摡念,不是指CPU),包含运行Go代码的必要资源,也有调度goroutine的能力。

GPM的关系:

1.M必须拥有P才可以执行G中的代码,交给线程进行执行。默认情况下等同于CPU的核数。一般情况下M的个数会略大于P的个数,这多出来的M将会在G产生系统调用时发挥作用。类似线程池,Go也提供一个M的池子,需要时从池子中获取,用完放回池子,不够用时就再创建一个
2.P含有一个包含多个G的队列,调度G交由M执行。默认情况下等同于CPU的核数
3.P可以调度G交由M执行

调度算法

image
  • G: 表示 Goroutine,每个 Goroutine 对应一个 G 结构体,G 存储 Goroutine 的运行堆栈、状态以及任务函数,可重用。G 并非执行体,每个 G 需要绑定到 P 才能被调度执行。

  • P: Processor,表示逻辑处理器, 对 G 来说,P 相当于 CPU 核,G 只有绑定到 P(在 P 的 local runq 中)才能被调度。对 M 来说,P 提供了相关的执行环境(Context),如内存分配状态(mcache),任务队列(G)等,P 的数量决定了系统内最大可并行的 G 的数量(前提:物理 CPU 核数 >= P 的数量),P 的数量由用户设置的 GOMAXPROCS 决定,但是不论 GOMAXPROCS 设置为多大,P 的数量最大为 256。

  • M: Machine,OS 线程抽象,代表着真正执行计算的资源,在绑定有效的 P 后,进入 schedule 循环;而 schedule 循环的机制大致是从 Global 队列、P 的 Local 队列以及 wait 队列中获取 G,切换到 G 的执行栈上并执行 G 的函数,调用 goexit 做清理工作并回到 M,如此反复。M 并不保留 G 状态,这是 G 可以跨 M 调度的基础,M 的数量是不定的,由 Go Runtime 调整,为了防止创建过多 OS 线程导致系统调度不过来,目前默认最大限制为 10000 个。

  • 每个 P 维护一个 G 的本地队列;

  • 当一个 G 被创建出来,或者变为可执行状态时,就把他放到 P 的本地可执行队列中,如果满了则放入Global;

  • 当一个 G 在 M 里执行结束后,P 会从队列中把该 G 取出;如果此时 P 的队列为空,即没有其他 G 可以执行, M 就随机选择另外一个 P,从其可执行的 G 队列中取走一半。

调度过程

当通过 go 关键字创建一个新的 goroutine 的时候,它会优先被放入 P 的本地队列。为了运行 goroutine,M 需要持有(绑定)一个 P,接着 M 会启动一个 OS 线程,循环从 P 的本地队列里取出一个 goroutine 并执行。执行调度算法:当 M 执行完了当前 P 的 Local 队列里的所有 G 后,P 也不会就这么在那划水啥都不干,它会先尝试从 Global 队列寻找 G 来执行,如果 Global 队列为空,它会随机挑选另外一个 P,从它的队列里中拿走一半的 G 到自己的队列中执行。

G0系统调用结束后,根据M0是否能获取到P,将会将G0做不同的处理:

1.如果有空闲的P,则获取一个P,继续执行G0。
2.如果没有空闲的P,则将G0放入全局队列,等待被其他的P调度。然后M0将进入缓存池睡眠。

调度策略:

1.队列轮转
2.系统调用
3.工作量窃取

go routine vs thread

内存占用:
1.goroutine 的栈内存消耗为 2 KB,实际运行过程中,如果栈空间不够用,会自动进行扩容,最大可达 1GB。(64 位机器最大是 1G,32 位机器最大是 256M)
2.thread 则需要消耗 1 MB 栈内存,而且还需要一个被称为 “a guard page” 的区域用于和其他 thread 的栈空间进行隔离
创建和销毀:
1.goroutine 直接从runtime申请,用户级
2.thread 需要进行系统调用
切换:
1.goroutines 切换只需保存三个寄存器:Program Counter, Stack Pointer and BP。
2.threads 切换时,需要保存各种寄存器
因此goroutines切换速度比thread快很多

为什么要 scheduler:

根据scheduler的功能来说明:

  1. 因为所有的ggoroutines 都通过runtime维护,而众多的goroutines需要依赖thread才能执行,因此使用scheduler来对goroutines的运行进行调度控制已达到高效的执行效率。
  2. 若没有scheduler,比如1:1模型,在同一时刻,一个线程上只能跑一个 goroutine,当 goroutine 发生阻塞,其他goroutine就会被阻塞,大大影响了性能,而有了scheduler,被阻塞的线程会被调度走,让其他的goroutine来执行。
  3. 如果非要杠,没有scheduler也能运行,但是无法达到高效的目的。

Go scheduler的作用

Go scheduler 的职责就是将所有处于 runnable 的 goroutines 均匀分布到在 P 上运行的 M。
Go scheduler 每一轮调度要做的工作就是找到处于 runnable 的 goroutines,并执行它

M:N模型 和工作窃取

在任一时刻,M 个 goroutines(G) 要分配到 N 个内核线程(M),这些 M 跑在个数最多为 GOMAXPROCS 的逻辑处理器(P)上。每个 M 必须依附于一个 P,每个 P 在同一时刻只能运行一个 M。如果 P 上的 M 阻塞了,那它就需要其他的 M 来运行 P 的 LRQ 里的 。当 P2 上的一个 G 执行结束,它就会去 LRQ 获取下一个 G 来执行。如果 LRQ 已经空了,就是说本地可运行队列已经没有 G 需要执行,并且这时 GRQ 也没有 G 了。这时,P2 会随机选择一个 P(称为 P1),P2 会从 P1 的 LRQ “偷”过来一半的 G。

GPM是什么

G、P、M 是 Go 调度器的三个核心组件

go初始化流程:

1.call osinit。初始化系统核心数。
2.call schedinit。初始化调度器。
3.make & queue new G。创建新的 goroutine。
4.call runtime·mstart。调用 mstart,启动调度。
5.The new G calls runtime·main。在新的 goroutine 上运行 runtime.main 函数。

scheduler初始化过程:

1.调整 SP
2.初始化 g0 栈
3.主线程绑定 m0
4.初始化 m0
5.初始化 allp

main goroutine是如何诞生的?

main goroutine执行流程:

1.创建后台监控线程sysmon
2.runtime包初始化
3.main包初始化
4.执行用户main函数
5.调用exit(0)退出

普通 goroutine执行流程:

先是跳转到提前设置好的 goexit 函数的第二条指令,然后调用 runtime.goexit1,接着调用 mcall(goexit0),而 mcall 函数会切换到 g0 栈,运行 goexit0 函数,清理 goroutine 的一些字段,并将其添加到 goroutine 缓存池里,然后进入 schedule 调度循环。到这里,普通 goroutine 才算完成使命

g0栈和用户栈如何切换?

这中间涉及到栈和寄存器的切换

如何保存g0的调度信息?

schedule函数有什么重要作用?
gogo函数如何完成从g0到main goroutine的切换?

schedule如何循环运转?

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

g0 栈的作用就是为运行 runtime 代码提供一个“环境”。

M如何找工作

1.先从本地队列找
2.定期会从全局队列找
3.最后实在没办法,就去别的 P 偷

sysmon后台监控线程作用(执行监控任务):

1.抢占处于系统调用的 P,让其他 m 接管它,以运行其他的 goroutine
2.将运行时间过长的 goroutine 调度出去,给其他 goroutine 运行的机会

sysmon函数的作用

sysmon 执行一个无限循环,一开始每次循环休眠 20us,之后(1 ms 后)每次休眠时间倍增,最终每一轮都会休眠 10ms;
sysmon 中会进行 netpool(获取 fd 事件)、retake(抢占)、forcegc(按时间强制执行 gc),scavenge heap(释放自由列表中多余的项减少内存占用)等处理

相关文章

  • Go调度相关

    go 调度go routinue在线程中进行调度 GPM的概念: G(Goroutine): 即Go协程,每个go...

  • 谈谈Go的调度实现

    谈谈Go的调度实现 本章主要针对Go调度相关介绍。仅关注linux系统下的逻辑。代码版本GO1.9.2。 本章例子...

  • Go 语言调度(一): 系统调度

    调度相关的一系列文章主要参考 Scheduling In Go : Part I - OS Scheduler 翻...

  • [Go学习笔记]调度

    调度相关的一系列文章主要参考 Scheduling In Go : Part I - OS Scheduler 翻...

  • Golang后端面试汇总-001

    基础面试 go的调度 为什么在内核的线程调度器之外Go还需要一个自己的调度器? go struct能不能比较 go...

  • Golang调度器

    本文尽量通俗易懂地讲Go调度器(scheduler)的相关知识,尤其是普通开发者能够关注和控制的部分。调度器本身十...

  • Go内存管理源码浅析

    前一篇讲了Go的调度机制和相关源码,这里说一下内存的管理,代码片段也都是基于Go 1.12。 简要的背景 一个程序...

  • 2019-03-20

    1. go的并发调度模型? go的并发调度模型可以简称为GPM模型,其中G代表goroutine,P代表gorou...

  • 高伸缩性Go调度器设计(译)

    阅读该文档前假设你已经对go语言及其当前调度实现的有所了解 当前调度器所存在的问题 当前的调度器限制了go并发的伸...

  • Go并发调度

    本文是《循序渐进Go语言》的第六篇-Go并发调度。本文是学习《Go语言学习笔记》的并发调度一章,然后结合阅读源码的...

网友评论

      本文标题:Go调度相关

      本文链接:https://www.haomeiwen.com/subject/tlfreltx.html