GMP调度模型

GMP的发展: go 1.1版本之前时候过使用的是GM模型+全局队列的模式。

GM模型+全局队列的模式

M:1 = 内核线程:协程

新建一个协程G的时候会放入全局队列中,每次执行一个协程G的时候,内核线程M会从全局队列中获取一个协程G执行,因为内核线程M存在多个所以存在并发问题,因此每次从队列中取协程G的时候都要加锁,所以当高并发的时候就会存在性能问题。

解决办法:给内核线程分配一个协程队列

GMP模型 + 全局队列

M:N:=内核线程 :协程

p的个数对应的是设置的内核个数

GMP模型

G -> goroutine(协程)

P -> Processor 调度器(如果线程想运行goroutine,必须先获取P,P中还包含了可运行的G队列)(注意不是cpu)

M -> thread 内核线程(每个M都代表了1个内核线程,OS调度器负责把内核线程分配到CPU的核上执行。)

模型简介

调度器的设计策略

并行(多核多线程来实现并行)、抢占(限定G与M的绑定时间,最多10ms,超过时间解绑,避免其他协程饥饿)、复用线程(偷取 + 分离)、减少全局竞争

有多个P M来执行多个G;p本地队列最多256g

新建一个协程G会优先放到本队队列中,如果本队队列P满了,则会把G放入到全局队列中,本地队列为空的时候,就会从全局队列中获取,如果全局队列为空的话,就会从其他队里中拿协程到自己的本地队列中。如果中途携程阻塞了,本地队列会在其他的内核线程上运行。

注意:M的数量和P的数量没有关系。如果当前的M阻塞,P的goroutine会运行在其他的M上,或者新建一个M。所以可能出现有很多个M,只有1个P的情况。

“go func()”经历了什么过程

调度器的生命周期

GO生命周期:创建、保存、被获取、调度执行、阻塞、销毁

CPU感知不到协程,GO调度器把协程调度到内核线程上去,然后操作系统调度器将内核线程放到cpu上执行;m是内核线程的封装。go调度器工作就是将G分配到M

M的几种状态:休眠(未绑定P)、运行、自旋(绑定了p 只是没有可执行G)。

运行+自旋<= gomaxproc数量

需要注意的点:

M和P不是绝对的1:1 ,有G阻塞的时候,M也会阻塞,会有新的M来继续执行P队中的G

p队列是先进先出

work stealing时,从偷取的P那里,取尾部的一半放到自己的P中

从全局队列中取时,取n个,len(GQ)为全局队列G的个数,公式: n = min(len(GQ)/GOMAXPROCS + 1, len(GQ/2))

本地队列满了,又创建新的G,此时把本地队列的前一半与新创建的G,打乱后,一起放到全局队列中

自旋线程的最大限制不能超过GOMAXPROCS,多出的线程休眠(⻓时间休眠等待GC回收销毁)

原文链接:https://blog.csdn.net/qq_42956653/article/details/121234816

扩展

  1. 如何控制同一时间的并发执行数,不能让程序崩溃?

(1)使用sync.WaitGroup控制协程数量。不是动态控制法,只是控制并发了

(2)使用sync.Mutex控制协程数量 ,这个待验证

(3) 使用带缓冲的通道限制并发数 。创建chan的时候手动设置缓存: c:=make(chan int, 2), 在协程创建前写入chan在协程结束前读出chan,达到限制并发数的需求

有关M和P的个数问题

  1. P的数量:

  • 由启动时环境变量 $GOMAXPROCS 或者是由 runtime 的方法 GOMAXPROCS() 决定。这意味着在程序执行的任意时刻都只有 $GOMAXPROCS 个 goroutine 在同时运行。

  1. M的数量:

  • go 语言本身的限制:go 程序启动时,会设置 M 的最大数量,默认 10000. 但是内核很难支持这么多的线程数,所以这个限制可以忽略。

  • runtime/debug 中的 SetMaxThreads 函数,设置 M 的最大数量

  • 一个 M 阻塞了,会创建新的 M。

M 与 P 的数量没有绝对关系,一个 M 阻塞,P 就会去创建或者切换另一个 M,所以,即使 P 的默认数量是 1,也有可能会创建很多个 M 出来。

P 和 M 何时会被创建?

  1. 在确定了 P 的最大数量 n 后,运行时系统会根据这个数量创建 n 个 P。

  1. 没有足够的 M 来关联 P 并运行其中的可运行的 G。比如所有的 M 此时都阻塞住了,而 P 中还有很多就绪任务,就会去寻找空闲的 M,而没有空闲的,就会去创建新的 M。

调度器的设计策略?

复用线程:避免频繁的创建、销毁线程,而是对线程的复用。

  1. work stealing 机制

  • 当本线程无可运行的 G 时,尝试从其他线程绑定的 P 偷取 G,而不是销毁线程。

  1. hand off 机制

  • 当本线程因为 G 进行系统调用阻塞时,线程释放绑定的 P,把 P 转移给其他空闲的线程执行。

利用并行:GOMAXPROCS 设置 P 的数量,最多有 GOMAXPROCS 个线程分布在多个 CPU 上同时运行。GOMAXPROCS 也限制了并发的程度,比如 GOMAXPROCS = 核数/2,则最多利用了一半的 CPU 核进行并行。

抢占:在 coroutine 中要等待一个协程主动让出 CPU 才执行下一个协程,在 Go 中,一个 goroutine 最多占用 CPU 10ms,防止其他 goroutine 被饿死,这就是 goroutine 不同于 coroutine 的一个地方。

全局 G 队列:,当 M 执行 work stealing 从其他 P 偷不到 G 时,它可以从全局 G 队列获取 G。

调度器的生命周期?

M0

M0 是启动程序后的编号为 0 的主线程,这个 M 对应的实例会在全局变量 runtime.m0 中,不需要在 heap 上分配,M0 负责执行初始化操作和启动第一个 G, 在之后 M0 就和其他的 M 一样了。

G0

G0 是每次启动一个 M 都会第一个创建的 gourtine,G0 仅用于负责调度的 G,G0 不指向任何可执行的函数,每个 M 都会有一个自己的 G0。在调度或系统调用时会使用 G0 的栈空间,全局变量的 G0 是 M0 的 G0。

可视化 GMP 编程?

方式 1:go tool trace 方式 2:Debug trace

更多详情点击查看点击这里

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值