谈谈对 GMP 的简单认识

犹记得最开始学习 golang 的时候,大佬们分享 GMP 模型的时候,总感觉云里雾里,听了半天,并没有一个很清晰的概念,不知 xmd 是否会有这样的体会

虽然 golang 入门很简单,但是对于理解 golang 的设计思想和原理,还是需要一定时间的积累和沉淀,更多的应该是思想上的沉淀

希望这篇文章能够对你了解 golang 的 GMP 模型有一点帮助

文章分别从一下三个方面来谈谈我对 GMP 模型认识

  • golang 中调度器的变化及其作用
  • 有了进程,线程,为什么会出现协程
  • GMP 模型中的 G,M,P 分别都做着什么样的事情

golang 中的调度器的变化及其作用

调度器,scheduler

怎么理解呢?调度器就像是一个管理者,负责安排事项,负责调度不同人在指定时间在某个岗位上完成自己的价值交付

正如 linux 调度器一样,将就绪的进程调度成执行状态,或者将执行状态的进程,打断,变成阻塞状态,再变成就绪状态

比如说一个经典的单进程 和 多进程/多线程的操作系统,

我们可以看到在单进程系统中,只需要无脑的将进程串行排列好, CPU 会串行去执行任务,如果遇到进程1 阻塞的情况,其他进程也没有办法被 cpu 执行,那么进程2 ,进程3 ,进程4 就都要等待前面的进程完成执行完毕,才能到自己执行

可以看出单进程对于 CPU 的使用过于任性,浪费 CPU 的资源,演进到多进程/多线程操作系统的时候,就出现了调度器

上图中我们可以对比看到,在多进程/多线程的操作系统中,cpu 的时间片被分割的更加的小,对于 cpu 资源的利用率是大大的增加了,因为 cpu 可以在进程1阻塞的时候,切换去执行进程2

例如,当进程1 执行过程中,发生了阻塞,那么调度器就会就会将 cpu 切换到进程2 中进行执行,同理,进程2 阻塞的时候, cpu 就会被切换到进程 3 进行执行,当然,这就看是哪个进程先抢到 cpu 资源了

可以看到,调度器在这里的作用就是最大限度的利用上 CPU 的资源,管理进程在 CPU 上按照一定的的顺序执行任务,就好比一个优秀的管家可以合理安排好不同的员工在指定的时间上专注的处理某项事务

那么 golang 的调度器是不是也是和 linux 中的调度器有着想通之处呢?

来看看调度器在 golang 中的具体作用是干啥的

在 golang 中,调度器的实现简单来看实际上是由协程和线程按照一定的逻辑来组合起来的,其实也是扮演着一个协调和调度的作用,调度的对象是协程和线程,协程是需要被调度到线程中来运行的,这个动作就是调度器干的

  • 通常用 G 来表示协程
  • 用 M 来表示线程

正如是这样来实现调度器的:

我们可以看到,当每一个 M 想要从全局队列中取 G 出来执行的时候,是需要访问全局队列的锁,这是一把互斥锁,同一时间只能有 1 个 M 在访问

Golang 的调度器此处当然也能让多个 M 处理不同的 G,达到多进程/多线程 并发处理事项的目的

可我们知道,加锁,是会影响到我们系统的整体性能的,毕竟那么多 M 都在竞争这一把锁,势必同一时间没有抢到的锁的 M 就要等待了

为什么调度器会被淘汰掉?

看到上图,细心的我们可以发现,

所以,这个调度器缺点也是比较明显的:

第一,多个 M 都要并发的去获取全局队列中的 G ,会造成锁竞争 , 此处操作全局队列的情况有如下几种情况

  • 创建 G
  • 调度 G
  • 销毁 G

第二, 线程 M 在执行 G 的过程中,如果 G 阻塞了,那么 M 也就阻塞了,这个时候,CPU 就需要切换到其他的 M 上执行 G,这个切换的过程也是对于 CPU 资源的浪费

正如人的大脑,同时处理多个事项,当 事项 1 未处理完毕,就去处理事项 2,那么需要重新理清思绪去执行,若这个时候又需要切换到 事项 1 的时候,就需要花更多的时间来回顾之前事项 1 处理的上下文了, 所以在工作中,没有特别必要,不要打扰别人

第三, 我们知道在 golang 中,一个 G 也是可以再创建 G 的,就叫 G1 吧,那么创建的这个 G1 就需要交给其他的 M 来进行执行了,因为当前的 M 正在执行 G,自己忙不过来,因此需要将 G1 交给 M1 执行

这也是一个很明显的问题

正如上述例子一样, 2 个人排查相关问题,但是信息和认知不一致,很明显是小猪可能是比较难排查出问题来的

就像 M 执行 G 新创建了 G1,但是是交给 M1 来执行的, M1 并不知道 G1 和 G 的关系,M1 执行起来就可能会出现问题,例如需要 G 的一些上下文,但是 M1 并不知情

因此,随着时间的推移,对于性能的要求是越来越高,当然是要想办法换一个与时俱进,需要符合时代潮流的管家了,自然就出现了一个新的调度器替代了原有的调度器

出现新的调度器,自然是解决了旧的调度器的缺点,并且还带来了一些新的属性和价值,具体的新调度器策略我们可以在下文中进行展示

为什么会出现协程

在来看另外一个问题,为什么会出现协程,自然是因为使用进程和线程不能够满足我们的某些需求了,此处的需求是指对于性能的要求,是对 CPU 利用效率的需求

上图中我们有说到,对于多进程/多线程并发的时候,我们有提高 CPU 的利用率,尽可能的利用好 CPU 的时间片,但是对于 CPU 从 进程/线程1 切换到去执行进程/线程 2 的过程中,是会产生消耗的,但是这个消耗很难避免

那么我们知道 CPU 其实执行的是线程,那么如果 1 个线程里面还可以分成多个程序进行并发岂不是可以大大的提高我们当前线程的使用效率?

我们从 C/C++ 中知道,咱们的一个32位系统的机器,进程实际上是开辟了一个 4G 的虚拟空间,具体 4G 虚拟空间都包含了什么,我们可以简单的看看这个图,本次不在此细聊

那线程大概也是需要 4M 的空间,那么我们在一定程度上大量的开辟进程或者线程,必然会带来系统中 内存占用高,调度 CPU 切换的消耗高 的问题

后来,我们知道线程实际上是分为内核态和用户态的

当然,用户态的线程是依赖于内核态线程的,用户态中需要执行的内容,是需要放在内核态线程上进行执行的,另外, CPU 只知道有内核态线程的存在,意味着,CPU 只认内核态线程

此处说的内核态,和用户态,其实就是对应到我们说的 M 和 G

  • 内核态线程 – 线程
  • 用户态线程 – 协程

协程不会陷入到内核态中,因此在 M 不变的情况下,切换 G 就是非常轻快的了,协程简要有如下特点:

  • 占用内存空间小,只占用 几 kb ,比起进程,线程来说,真的是很小了
  • 调度灵活,他是处于用户态进行调度的

根据协程和线程处于的用户态和内核态,我们可以看到调度的机制是不一样的,

内核态中的线程,实际上是抢占式的,是又 CPU 调度的

用户态中的线程,即协程,是由用户态调度的,此处的用户态调度,就可以理解是一个队列了,只能一个一个的去执行了,一个协程执行完毕之后,让出 CPU ,才会执行下一个协程

好比我们代码中,多个协程,其中 1 个协程 panic 了,如何不捕获的话,是不是整个程序都崩溃了

对于 CPU 只认内核态线程这一点,咱理解起来就想到与一个公司的老板,只认每个产品线上的总监一样,你这个主管后面不管有多人少人在干活,老板只认为是你这个主管在干活

当然一个公司也不仅仅只有 1 个老板,就像计算机系统里面也会有多个 CPU ,但是道理是一样的

如何理解 GMP 模型

GMP 分别表示 协程 goroutine,线程 thread,处理器 processor

他们三组成了新的调度器,他们三者的关系这样的

咱们从图中可以知道有全局队列,本地队列

全局队列还是和之前一样,从里面里面 G 的创建,调度,销毁 都是需要访问互斥锁的

P 的本地队列 自己维护了一个 G 列表,最长是 256 个那 P 的个数一般是 GOMAXPROCS 个,如果本地的队列满了,那么 P 会将队列中的一半给到全局队列中

从本地队列中取 G 是不需要加锁的,直接取即可,且如果队列中的 G 又创建了 G1 ,那么这个 G1 也是会被优先加入到当前本地队列的

此处我们可以看到 M 是和 P 进行对应了,当 M 需要运行 G 的时候,就需要先找到一个合适的 P ,从 P 中获取 G, 如果 P 为空,那么 M 就会从全局队列中获取一批 G 放到 P 的队列中

或者是 M 也会从其他的 P 的队列中偷一半的 G 到当前 P 的队列中

从这里我们可以知道,新的调度器,已经解决了旧的调度器的一部分问题了(不需要每次都去找全局队列)

那么 P 是啥时候创建,创建多少个?M 又是啥时候创建的,创建多少个呢? P 和 M 在数量上有必然联系吗?

P 是在程序启动的时候,读取环境变量中 GOMAXPROC ,来创建具体 P 的个数

M 的创建实际上是,若 P 中有很多任务 G ,如果有空闲的 M,那么 P 就会找任意空闲 M 来进行处理,如果没有空闲的 M ,那么调度器就会去创建 M 来执行 P 中的 任务

那么新的调度器还有哪些优势呢?

第一

提高了线程的复用率,如果当前线程执行完当前 P 的任务之后,当前的 M 会尝试去偷其他 P 里面的 G 来进行执行,这样我们就尽可能的避免了线程的创建,销毁带来的开销

第二

如果当前的 M 在执行 G 的时候,出现了阻塞,那么 M 也会很懂事的让出当前的 P,让其他 M 来执行 P 中的任务当前的 M 就继续处理当前的 G

第三

上述我们说到当前 M 会去从其他 P 的队列中偷 G,这个是在当前 M 对应的 P 中没有 G 的时候,优先去做的事情, 如果其他的 P 也没有 G 的时候,当前 M 才会去全局队列中拿

从这里就可以开始,新的调度器,已经是在大大的弱化了全局队列的作用

本次先聊到这里,相信你对 GMP 的基本理论也有一些了解了吧

感谢阅读,欢迎交流,点个赞,关注一波 再走吧

可以进入地址进行体验和学习:https://xxetb.xet.tech/s/3lucCI

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.rhkb.cn/news/102468.html

如若内容造成侵权/违法违规/事实不符,请联系长河编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

python、numpy、pytorch中的浅拷贝和深拷贝

1、Python中的浅拷贝和深拷贝 import copya [1, 2, 3, 4, [11, 22, 33, [111, 222]]] b a c a.copy() d copy.deepcopy(a)print(before modify\r\n a\r\n, a, \r\n,b a\r\n, b, \r\n,c a.copy()\r\n, c, \r\n,d copy.deepcopy(a)\r\n, d, \r\n)before modify a [1, 2…

从C语言到C++_33(C++11_上)initializer_list+右值引用+完美转发+移动构造/赋值

目录 1. 列表初始化initializer_list 2. 前面提到的一些知识点 2.1 小语法 2.2 STL中的一些变化 3. 右值和右值引用 3.1 右值和右值引用概念 3.2 右值引用类型的左值属性 3.3 左值引用与右值引用比较 3.4 右值引用的使用场景 3.4.1 左值引用的功能和短板 3.4.2 移动…

static相关知识点详解

文章目录 一. 修饰成员变量二. 修饰成员方法三. 修饰代码块四. 修饰类 一. 修饰成员变量 static 修饰的成员变量,称为静态成员变量,该变量不属于某个具体的对象,是所有对象所共享的。 public class Student {private String name;private sta…

【C++杂货铺】探索string的底层实现

文章目录 一、成员变量二、成员函数2.1 默认构造函数2.2 拷贝构造函数2.3 operator2.4 c_str()2.5 size()2.6 operator[ ]2.7 iterator2.8 reserve2.9 resize2.10 push_back2.11 append2.12 operator2.13 insert2.14 erase2.15 find2.16 substr2.17 operator<<2.18 opera…

【路由协议】使用按需路由协议和数据包注入的即时网络模拟传递率(PDR)、总消耗能量和节点消耗能量以及延迟研究(Matlab代码实现)

&#x1f4a5;&#x1f4a5;&#x1f49e;&#x1f49e;欢迎来到本博客❤️❤️&#x1f4a5;&#x1f4a5; &#x1f3c6;博主优势&#xff1a;&#x1f31e;&#x1f31e;&#x1f31e;博客内容尽量做到思维缜密&#xff0c;逻辑清晰&#xff0c;为了方便读者。 ⛳️座右铭&a…

Windows使用MobaXterm远程访问ubuntu20.04桌面

参考ubuntu 2020.4 安装vnc 一、脚本文件 remote_setup.sh脚本文件内容&#xff1a; #! /bin/bash #参考链接&#xff1a;https://blog.csdn.net/hailangdeyingzi/article/details/124507304 sudo apt update sudo apt install x11vnc -y sudo x11vnc -storepasswd telpo.12…

二、11.系统交互

fork 函数原型是 pid_t fork(void&#xff09;&#xff0c;返回值是数字&#xff0c;该数字有可能是子进程的 pid &#xff0c;有可能是 0&#xff0c;也有可能是-1 。 1个函数有 3 种返回值&#xff0c;这是为什么呢&#xff1f;可能的原因是 Linux 中没有获取子进程 pid 的方…

主程技术分享: 游戏项目帧同步,状态同步如何选

网络游戏开发项目中帧同步,状态同步如何选&#xff1f; 网络游戏的核心技术之一就是玩家的网络同步,主流的网络同步有”帧同步”与”状态同步”。今天我们来分析一下这两种同步模式。同时教大家如何在自己的项目中采用最合适的同步方式。接下来从以下3个方面来阐述: 对啦&…

如何拉取Gitee / GitHub上的Unity项目并成功运行

前言 由于目前大部分人使用的仓库都是Gitee或者是GitHub&#xff0c;包括小编的公司所使用的项目仓库也包括了Gitee&#xff1b;我们需要学习技术栈时都会去百度或者是去GitHub上看看别人的项目观摩学习&#xff0c;可能很多小白在遇到拉取代码时出现各种问题&#xff0c;或者…

外包干了2年,彻底废了...

先说一下自己的情况。大专生&#xff0c;17年通过校招进入湖南某软件公司&#xff0c;干了接近2年的点点点&#xff0c;今年年上旬&#xff0c;感觉自己不能够在这样下去了&#xff0c;长时间呆在一个舒适的环境会让一个人堕落&#xff01;而我已经在一个企业干了五年的功能测试…

mongodb集群

端口192.168.115.3 192.168.115.4 1192.168.115.5 下载MongoDB软件包版本为4.2.14并安装 rpm -ih --force --nodeps *.rpm 2创建文件夹mkdir -p /opt/local/mongo-cluster/conf 3.在目录里创建配置文件cd /opt/local/mongo-cluster/conf …

线性数据结构:数组与链表的探索与应用

文章目录 1. 数组&#xff1a;连续存储的有序元素集合1.1 创建和访问数组1.2 数组的搜索与排序 2. 链表&#xff1a;非连续存储的动态数据结构2.1 单链表与双链表2.2 链表的操作与应用 3. 数组与链表的比较与应用3.1 数组与链表的比较3.2 数组与链表的应用 4. 总结与展望 &…

leetcode414. 第三大的数

题目&#xff1a; 给你一个非空数组&#xff0c;返回此数组中 第三大的数 。如果不存在&#xff0c;则返回数组中最大的数。 示例 1&#xff1a; 输入&#xff1a;[3, 2, 1] 输出&#xff1a;1 解释&#xff1a;第三大的数是 1 。 示例 2&#xff1a; 输入&#xff1a;[1, …

字符设备驱动实例(ADC驱动)

四、ADC驱动 ADC是将模拟信号转换为数字信号的转换器&#xff0c;在 Exynos4412 上有一个ADC&#xff0c;其主要的特性如下。 (1)量程为0~1.8V。 (2)精度有 10bit 和 12bit 可选。 (3)采样时钟最高为5MHz&#xff0c;转换速率最高为1MSPS (4)具有四路模拟输入&#xff0c;同一时…

【LVS集群】

目录 一、集群概述 1.负载均衡技术类型 2.负载均衡实现方式 二、LVS结构 1.三层结构 2.架构对象 三、LVS工作模式 四、LVS负载均衡算法 1.静态负载均衡 2.动态负载均衡 五、ipvsadm命令详解 1. -A 2. -D 3. -L 4. -a 5. -d 6. -l 7. -t 8. -s 9. -r 10. -…

css 实现四角边框样式

效果如图 此图只实现 左下与右下边角样式 右上与左上同理 /* 容器 */ .card-mini {position: relative; } /* 左下*/ .card-mini::before {content: ;position: absolute;left: 0;bottom: 0;width: 20px;height: 20px;border-bottom: 2px solid #253d64;border-left: 2px so…

redis 6个节点(3主3从),始终一个节点不能启动

redis节点&#xff0c;始终有一个节点不能启动起来 1.修改了配置文件 protected-mode no&#xff0c;重启 修改了配置文件 protected-mode no&#xff0c;重启redis问题依然存在 2、查看/var/log/message的redis日志 Aug 21 07:40:33 redisMaster kernel: Out of memory: K…

什么是神经网络

什么是神经网络 什么是神经网络&#xff1f;CNN、RNN、GNN&#xff0c;这么多的神经网络&#xff0c;有什么区别和联系&#xff1f; 既然我们的目标是打造人工智能&#xff0c;拥有智慧的大脑无疑是最好的模仿对象&#xff0c;人脑中有约860亿个神经元&#xff0c;这被认为是…

9 - 蓝图

蓝图: 将项目分成一个个单独的app模块&#xff0c;然后将所有app分配不同的处理功能&#xff0c;通过路由分配将它们连接成一个大项目 目录结构: 搭建框架: (1). 新键apps 包,编辑__init__.py文件 from flask import Flask import settings from apps.user.view import user_b…

03_缓存双写一致性

03——缓存双写一致性 一、缓存双写一致性 如果redis中有数据&#xff0c;需要和数据库中的值相同如果redis中无数据&#xff0c;数据库中的值要是最新值&#xff0c;且准备回写redis 缓存按照操作来分&#xff0c;可以分为两种&#xff1a; 只读缓存 读写缓存 同步直写操作…