Golang-GMP调度模型

今天我们一起来聊一聊go语言高并发背后的底层原理------调度模型以及调度策略。

GM模型

GM模型是V1.1版本之前的调度模型,由于存在严重的锁竞争问题,后来被废弃了。其中:

  • G:协程。
  • M:内核态线程,由操作系统进行调度,在CPU上运行。

协程G相当于是用户态的线程,CPU感知不到用户态线程的存在并且用户态线程不能够被CPU调度执行,必须通过内核态线程来运行。用户态线程和内核态线程之间的关系有1:1、1:N、M:N。

在GM调度模型中,所有的G都存放在一个全局协程队列中,全局队列由于是多个M的共享资源,因此需要加上一个用于同步以及互斥作用的锁。

M想要执行G、放回G都需要访问全局队列,并且M有多个,为了防止多线程并发访问资源的不安全,每次访问时都需要加锁、释放锁。

因此,GM调度模型存在以下缺点:

  • 创建、销毁、调度G都需要每个M获取锁,这形成了激烈的锁竞争。
  • M转移派生的G时,会造成延迟和额外的系统负担。比如:当G运行过程中新创建了协程G',需要先将新创建的G'放入到全局队列中,之后等待其他M从全局队列中取出G'。一存一取,需要两次加锁操作,这会让G'的运行延迟并造成额外的系统开销。
  • 系统调用导致频繁的线程阻塞和取消阻塞操作增加了系统开销。

GMP模型

为了避免GM调度模型的问题,Go语言在V1.1版本中又引入了P,形成了GMP调度模型。其中:

  • G:协程。
  • M:内核态线程,由操作系统进行调度,在CPU上运行。
  • P:处理器,包含运行G的必要环境和本地队列,一个M只有持有P的时候才能够运行G。

在GMP模型中,M在运行G的过程中,如果派生出了新的协程,优先放置到P的本地队列当中,只有当本地队列已满的时候,才会放入全局队列当中。通过本地队列和轮训调度策略,减少派生协程的延迟执行和系统开销。

GMP调度策略

策略1:复用线程

移交机制 hand off:

如果M在执行G的过程中如果发生了系统调用或阻塞,这时候,会释放掉自身所持有的P,交给一个空闲的M或新创建的M(没有空闲则新建),继续执行P本地队列中的G。

窃取机制 work stealing:

新创建的G优先放置到创建者的本地队列中,这就可能会造成多个处理器P的本地队列G的数量是不均匀的,导致部分P非常繁忙,而部分P空闲。为了充分利用CPU资源,当P没有需要调度的G时,会从其他队列中窃取一半的G放入到本地队列中。

策略2:利用并行

通过 GOMAXPROCS 设置 P 的数量,最多有 GOMAXPROCS 个线程分布在多个 CPU 上同时运行。

策略3:抢占式调度

M持有P之后,会依次轮转本地队列中的G。在这个过程中,为避免某个协程长时间执行,而阻碍其他协程被调度的情况。处理器会监测每个协程的执行时长,一旦执行时间过长的话,P会把协程暂停,去调度其他的协程执行。

策略4:全局队列

只有当M的本地队列为空时,才会访问全局队列,从全局队列中获取一部分G放入到自己的本地队列中。

相关推荐
子兮曰14 小时前
jev-ultrafast 深度解析:7 秒订机票的浏览器 Agent 是如何炼成的
前端·后端·agent
子兮曰14 小时前
Jev 爆发一周:7 秒 Agent 背后的 System One 生态与三场争议
前端·后端·ai编程
小羊没烦恼!14 小时前
初探性能优化——2个月到4小时的性能提升
java·开发语言·windows·算法·c#
爱勇宝14 小时前
ZCode 开源 24 小时:一份没有历史的账本,回答不了"有没有偷代码"
前端·后端·chatglm (智谱)
胡写代码15 小时前
别再前后端各写一套表单校验了
java·后端
伞伞悦读15 小时前
【第38期】Python 模块与包详解:import、from、模块搜索路径、包结构和 __init__
开发语言·python
ttwuai15 小时前
Go开源后台管理系统推荐:怎么按技术栈和边界比较4个官方仓库?
golang·gin
大勇前进16 小时前
原生 PHP 还是 Laravel?小项目到底要不要上框架
后端
yuzhi_liu16 小时前
我用 LangGraph4j 实现 Multi-Agent Supervisor
后端
alsmile16 小时前
Node-RED 之外,国产规则引擎的新方案:基于标准语法,Go 先行实现
后端·开源·go