当前位置: 首页 > news >正文

Go 语言的 GMP 模型

序言

 谈到 Go 语言就一定离不来他的高并发能力,谈到他的高并发能力就一定离不开一个概念 — 协程。这对大家可能是一个陌生的概念,我们在操作系统中学习到了进程,线程,但是很少听过协程。通过这篇文章,让大家理解 Go 语言这个重要的拼图。


一、协程是什么

1. 回顾发展历史

 高并发能力的发展,其实就是一个既要又要的过程。在提供高性能的同时,我还希望能够尽量的节约系统资源。从原来的多进程、多线程实现高并发,到现在的多协程实现高并发也确实实现了模型越来越轻量,能力越来越强大。让我们来看看发展历程吧:

1.1 多进程的出现

 随着多核 CPU 的出现,最开始是使用多进程来并发处理任务,解决单任务顺序执行效率低的问题。但是多进程的缺点也非常明显:

  • 创建和销毁的开销非常大。进程是资源分配的单位,每创建一个进程都需要分配对应的资源
  • 进程切换开销大。开销主要在上下文的切换和恢复,以及 CPU 缓存实效
  • 进程间通信复杂。因为进程间是相互独立的,所以不具备直接通信的环境

那是不是多进程没有优点呢?非也:

  • 安全性高。进程间是相互独立的,所以一个进程的崩溃不会影响到其他进程

为了解决上述多进程存在的问题,多线程出现了。

1.2 多线程的出现

 现在对于一个线程的定义是,线程是进行调度的单位,一个进程中可以包含多个线程,多个线程共享同一个进程下的资源。多线程的出现大大降低了并发的成本,比起进程一个线程只需要创建私有的资源(寄存器状态以及私有栈)其他的资源都是共享进程的。优点如下:

  • 比进程轻量,创建和销毁的开销小
  • 切换的成本比进程小,只需要切换私有的数据
  • 具有天然互相通信的环境,因为线程间共享同一个进程的资源

但是线程也存在不可忽视的缺点:

  • 多线程竞争资源容易引发死锁、竞态条件
  • 线程数量过多时切换成本依然很高
  • 线程栈占用较大内存(通常1MB左右)

这里我们具体关注第二个问题,线程切换成本。当系统中的线程数量增多时,操作系统会不断的切换线程保证每一个线程都能够得到执行,所以大部分的时间花在了切换线程上这自然是一种浪费,线程的切换成本不可忽视:
在这里插入图片描述
线程切换是一个复杂但必要的过程,它涉及到用户态 ↔ 内核态的切换、线程上下文的保存与恢复、调度器的选择等多个步骤。

1.3 多协程的出现

 为了进一步提升并发能力,产生了现在的协程,他也有一个熟悉的名字 用户级线程 。所谓的用户级线程也就是创建、调度、切换等操作都是在用户态下进行的,不需要陷入到内核态。并且协程比起线程还更加的轻量!主要体现在一个线程的栈大小在 1MB 左右,但是一个协程的初始化栈大小在 2KB 左右(Go 语言初始大小,可动态拓展),这也是协程在十万级别的量级依然抗打的原因!来比较一下协程和线程吧:

对比维度线程协程
大小1MB左右2KB左右(可动态拓展)
操作系统感知YESNO
切换成本涉及到用户态和内核态切换纯用户态

2. 协程的三种实现模型

 也许大家对与上面描述的 操作系统感知 会产生疑惑?回顾下,线程是调度的基本单位,他的创建,切换,销毁等一些列操作都是需要内核参与的,但是用户级线程的一系列操作是在用户态下进行的,对于内核来说我只是在执行程序,不关心程序的具体内容是什么,自然也不会感知到协程的存在。
 那么协程总归还是依赖线程来执行,他们之间的关系有三种:1:1, 1:N, M:N依次来介绍下:

2.1 1:1 模型

 这是最简单的模型,一个协程对应一个线程:
在这里插入图片描述优点是简单,协程之间不会相互阻塞;缺点也很明显,太奢侈了,每创建一个协程就要创建一个线程,仿佛回到了问题的起点…

2.2 N:1 模型

 多个协程对应一个线程:
在这里插入图片描述优点是很明显:

  • 协程切换的效率很高,都在用户态下进行

缺点也不容小觑:

  • 不能充分的利用多核 CPU
  • 一个协程阻塞会影响其他协程的执行
  • 多个协程共享一个线程的时间片,协程不能充分的执行
2.3 M:N 模型

 这个模型是现在被 Go 语言 所采用的,虽然解决了上述两者现存的缺点,但是实现起来比较复杂:
在这里插入图片描述
协程被分配到不同的线程上执行,除了兼具上述两种模型的优点,它还具备如下优点:

  • 一个协程阻塞后,可以分配到其他线程执行
  • 能够充分的利用多核CPU

现在我们了解了协程是什么,我们为什么需要协程,以及协程的三种模型,现在我们再来了解 Go 语言 的协程是怎么实现的。


二、GMP 模型

 现在我们了解了 Go 语言 的协程是使用的 M:N 模型,但是具体是怎么实现的呢?线程是怎么调度协程的呢?

1. 被废弃 GM 模型

 在正事介绍 GMP 模型之前,我们先来介绍一下他的前身 GM 模型:
在这里插入图片描述
设置了一个全局的队列,队列中存储着需要执行的协程,每一个线程首先去获取锁,然后取出协程执行。这个模型的优点是很简单,但是缺点如下:

  • 锁的竞争非常激烈,限制了整体的性能
  • 局部性不强,当一个协程创建了另外的协程,有可能不在同一个线程上执行,没有充分利用的利用到缓存

对于大锁问题一般可以有两种思路,降低锁的颗粒度以及将资源分区。

2. GMP 模型

 比起最初的 GM 新增了一个 P,这给我们带来了什么呢?
刘丹
 这里的 P 代表处理器,一个线程必须得到一个 P 才能获取协程执行

这句话非常的重要!如果不考虑环境因素,Go 在运行的时候最多支持的并行数量取决于 P 的数量,P 的默认值也一般是 CPU 的核心数。

那这里有多少个 P 就对应多少个 M 哦?不是的,假设一个 M 在执行一个 G 时因为各种原因被阻塞了(IO、系统调用等 ),那么这个 P 就会和当前 M 解除,去寻找当前有无空闲的 M,如果没有就创建一个 M 执行。

P 中存在一个本地队列,线程优先执行本地队列中的协程,当本地队列中的协程执行完了之后再会去获取全局队列中的协程,如果还没有就回去 偷取 其他 P 中的协程。

这属于上述解决上述解决大锁的第二个方法,资源分区。并且 *M * 寻找 G 执行逻辑是放在 G0 中的,每一个 M 创建的时候会自带一个 G0,所以一个 M 在执行的过程也是不断交替执行 GG0 的过程。

3. 调度场景解析

3.1 创建新的协程

 现在 M1 绑定了 P1 从本地队列中取出了 G1 执行,执行过程中 G1 创建了 G3。那么首先会考虑本地队列是否有新的空间,如果有的话直接放在本地队列(更好的局部性,充分的利用 CPU 中的 Cache 缓存),但是本地队列满了(256)的话,就会将队列中的一部分 G 以及新创建的 G 转移到全局队列中。

3.2 获取新的协程

 现在 M1G1 执行完毕了,需要通过执行 G0 调度新的协程,过程如下:
在这里插入图片描述
如果最后都没有获取到 G,那么 M 并不会立刻被销毁,而是被缓存到全局的一个线程池当中,而 P 也会被存储到全局的一个 P 列表当中。当产生新的 G 时,会唤醒一个 M 去结合 P 然后去寻找 G 执行。

这里补充一下:当没有获取到新的协程的时候,有些说法是会允许部分的 M 处于一个自旋的状态(比如 1 / 4 P 的数量),当有新的 G 的时候可以提升调度响应速度,减少唤醒休眠开销

3.3 调用了系统调用或被阻塞

 现在当 M1 结合 P1 正在执行 G1G1 进行了系统调用,这时 P1 会立刻解除绑定,然后如果他自己的本地队列,或者全局队列,再或者其他 P 的本地队列存在着可执行的 G,那么P1 就会绑定其他的 M 来继续执行,否则就会加入到全局的空闲 P 列表中。

首先会判断有无休眠的 M,如果有的话唤醒再和 P1 绑定,如果没有就会创建一个 M

M1G1 从系统调用恢复后,会尝试获取一个 P 来继续执行,如果没有获取到的话,就会把 G1 放到全局队列,然后自己就会被休眠。


三、总结

GMP 模型通过轻量的 goroutine、智能的线程管理与高效的调度策略,实现了高并发、低开销、资源利用率高的极致并发性能。

相关文章:

  • AI赋能把“杂多集合”转化为“理想集合”的数学建模与认知升级
  • jvm安全点(一)openjdk17 c++源码垃圾回收安全点信号函数处理线程阻塞
  • 电子电器架构 --- 整车造车阶段四个重要节点
  • Python实例题:Python百行制作登陆系统
  • PEG适用范围
  • Java并发编程面试题:基础(11题)
  • 单调栈和单调队列
  • C++(20): 文件输入输出库 —— <fstream>
  • 关于Android Studio for Platform的使用记录
  • Android Studio报错Cannot parse result path string:
  • 配置ssh服务-ubuntu到Windows拷贝文件方法
  • 远程主机状态监控-GPU服务器状态监控-深度学习服务器状态监控
  • 【C/C++】C++返回值优化:RVO与NRVO全解析
  • Java-反射(Reflection)
  • MoveIt Setup Assistant 在导入urdf文件的时候报错
  • math toolkit for real-time development读书笔记一三角函数快速计算(2)
  • JavaScript【4】数组和其他内置对象(API)
  • 清华大学大模型驱动的跨尺度空间智能研究最新综述:具身智能体、智慧城市和地球科学领域的进展
  • 跨国应用程序的数据存储方案常见的解决方案
  • 什么是PMBus
  • 《大风杀》导演张琪:为了不算计观众,拍了部不讨好的警匪片
  • 外交部:国际社会广泛理解和支持中方不同意台参加世卫大会的决定
  • 前四个月社会融资规模增量累计为16.34万亿元,比上年同期多3.61万亿元
  • “女硕士失踪13年生两孩”案进入审查起诉阶段,哥哥:妹妹精神状态好转
  • 多家外资看好中国市场!野村建议“战术超配”,花旗上调恒指目标价
  • 学者的“好运气”:读本尼迪克特·安德森《椰壳碗外的人生》