Golang 的 GMP:并发编程的艺术

前言

在 Golang 的并发编程中,GMP 是一个重要的概念,它代表了 Goroutine、M(线程)和 P(调度器)。这个强大的三位一体的并发模型使得 Golang 在处理并发任务时非常高效和灵活。通过 GMP 的组合,Golang 实现了一种高效的并发模型。它充分利用了多核处理器的优势,并通过轻量级的 Goroutine 实现了高并发的编程模式。但是GPM到底是怎么工作的呢?今天这篇文章就为您解开GPM的神秘面纱。

调度器的由来

单进程系统

图片

早期的计算机都是单进程操作系统,各个进程之间都是顺序执行,也就是进程A执行完了才能执行进程B。

「对于cpu来说,进程和线程是一样的,这里我们就不讨论进程和线程的区别了」。

存在的问题
  • 单一执行流程,计算机只能一个任务一个任务的处理。
  • 如果进程A阻塞,会带来很多cpu浪费的时间。

多进程/线程操作系统

基于以上的问题,于是就出现了多进程/线程操作系统。

图片

  • 系统把cpu分成了一段一段的时间片(微妙级别)。
  • cpu在第一个时间片执行进程A,然后切换到进程B执行,再切换到进程C,一直这样轮询的执行。
  • 因为cpu被分成的时间片是微妙级别的,所以直观的感觉就是进程A,B,C是在同时执行的。
  • 多进程/线程操作系统的确解决了阻塞的问题,但是又出现了新的问题。
存在的问题

图片

  • 因为cpu需要不断地进程A,B,C之间切换,切换肯定避免不了各种复制,计算等消耗,所以在切换过程中浪费掉了很多时间成本,所以「进程/线程越多」,切换「成本就越大」,也就越「浪费」。
  • 在这种模式下运行CPU在切换动作上浪费的时间成本大概是40%,只有60%的时间是在执行程序。
  • 进程和线程对内存的占用是比较大的,在32位的操作系统中,进程占用的虚拟内存大概是4GB,现成占用内存大概是4M。

图片

协程的诞生

对于一个线程来说其实分为两部分,「用户空间」和「内核空间」。

图片

图片

  • 内核空间主要是指操作系统底层,包括进程开辟,分配物理内存资源,磁盘资源等。
  • 用户空间主要是编码业务逻辑部分。
  • 于是有人想到能不能把线程的内核空间和用户空间分开。并且让他们互相绑定在一起。
  • 对于cpu来说,只需要关注内核空间的线程就可以了。

当然如果只是这样把用户空间的协程和内核空间的线程一一绑定还是没有解决问题的,如果开启的比较多,那么对应的线程也会跟着一起增加,cpu频繁切换的问题还是没有解决,于是就引入了「调度器」的概念。

引入调度器来在各个协程之间切换,cpu只需要关注内核空间的线程即可,这样「解决了cpu在各个协程之间不断切换的问题」。

存在的问题

这样设计虽然解决了cpu频繁切换的问题,但是如果协程A发生了阻塞,肯定会导致协程B无法被执行。而且如果计算机是多核,那么是无法利用到多核的优势的。显然是不合理的。

对于多核的计算机,在内核空间可以开启多个线程(具体开启几个由计算内核决定,人为无法控制),所以问题的核心点就转移到了协程调度器上面,不管是什么语言,「协程调度器」做的越好,相对的「cpu利用率」也就越高。

图片

go对协程的处理

内存控制和灵活调度
  • 首先golang对协程改名为gorountine,并且把多余的空间都去掉,控制每个协程的内存在几KB大小,所以golang可以开启大量协程。
  • golang对协程的调度非常灵活,可以经常在各个协程之间切换。

图片

go对早期调度器的处理(GM模型)

golang在早起调度器处理是比较简单的,具体流程如下:

图片

  • 首先会有一个全局的go协程队列,并且加锁,防止资源竞争。
  • M获取锁之后会去尝试执行gorountine,执行完毕再把gorountine重新放回队列中。
GM模型存在以下问题
  • 创建、销毁、调度G都需要每个M获取锁,这就形成了激烈的锁竞争。
  • M转移G会造成延迟和额外的系统负载。
  • 系统调用(cpu在M之间切换)导致频繁的线程阻塞和取消阻塞操作增加了系统开销。
  • 比如我再一个G中又开辟了一个G1,那么G1和G当然在一个M上执行是比较合适的,因为存在一些共享内存,但是显然这种调度模式是无法做到的 基于以上问题,golang针对这块做了一些改进,也就是我们今天的主角,GMP模型。

GMP模型

GMP模型简介

GMP模型主要指的是G(gorountine协程),M(thread线程),P(processor处理器)之间的关系。

全局队列

存放等待运行的G。

P的本地队列
  • 存放等待运行的G。
  • P的本地队列存放的G是有数量限制的,一般是不超过256G。
  • 如果创建一个G,是会优先放在p的本地队列中,如果满了则会放到全局队列中去。
P列表
  • 在程序启动的过程时创建。
  • 最多有GOMAXPROCS个(可配置)。
  • 可以通过环境变量$GOMAXPROCS来设置P的个数,也可以在程序中通过runtime.GOMAXPROCS()来设置。
M列表
  • 当前操作系统分配到当前go程序的内核线程数。
  • go语言本身,限制M的最大数量是10000。
  • 可以通过runtime/debug包中的setMaxThreads来设置。
  • 如果有一个M阻塞,则会创建一个新的M。
  • 如果有M空闲,那么会回收或者睡眠。

调度器的设计策略

复用线程
work stealing机制

图片

  • M1对应的P上面G1正在执行,G2和G3处于等待中的状态。
  • M2对应的P处于空闲状态。

这种情况下M2对应的P会从M1对应的P的本地队列中把G3偷取过来执行,提高CPU的利用率,这种机制叫做「work stealing机制」。

hand off机制

图片

如果M1和M2都在正常执行,但是M1对应的G1发生了阻塞,那么势必会影响到G2的执行,那么GMP是如何解决的呢?

图片

  • golang会新创建一个M3,用来接管之前的P1剩下的G(G2)。
  • M1和G1进行绑定再继续执行,执行完毕之后把M1设置为睡眠状态等待下一次被利用,或者直接销毁。
并行利用

并行利用其实比较好理解,其实也就是开启了多少个P,P的个数是有GOMAXPROCS来决定的,一般都会设置为 「CPU核数/2」。

抢占策略

对于传统的co-routine来说,如果一个C和cpu进行了绑定,那么只有他主动释放,另外一个C才能和cpu进行绑定。但是在golang中,如果一个G和cpu进行了绑定,那么时间限制最多为10ms,另外一个G就可以直接和cpu绑定。

抢占策略。

全局队列

图片

  • 全局队列的本质是对work stealing的一种补充。
  • 如上图,M2对应的本地队列没有G,会优先从M1的本地队列中偷取。
  • 如果M1的本地队列中也没有G,那么就会从全局队列中去偷取G3。
  • 因为全局队列涉及到加锁和解锁,所以效率相对要低一些。

go的启动周期(M0和G0)

要想了解go的启动周期,首先得了解M0和G0的概念。

M0
  • 在一个进程中是唯一的。
  • 启动程序后编号为0的主线程。
  • 在全局变量runtime.m0中,不需要在heap上分配。
  • 负责初始化操作和启动第一个G。
  • 启动第一个G之后,M0就和其他的M一样了。
G0
  • 在一个线程中是唯一的。
  • 每次启动一个M,都会第一个创建的gorountine,就是G0。
  • G0仅仅用于负责调度其他G,G0不指向任何可执行的函数。
  • 每个M都会有一个自己的G0。
  • 在调度或者系统调用的时候,会使用M切换到G0来调度。
  • M0的G0会放在全局空间。

执行流程

package main
import "fmt"func main() {fmt.Println("Hello World")
}

比如我们看上断代码的执行流程。

初始化操作

在执行到main函数之前,会有一些初始化的操作,比如创建M0,创建G0等等。

图片

执行具体函数

当执行main函数的时候,M0已经和其他的M是一样的了,main函数会进入M0对应的p的本地队列中,然后和M0绑定执行,如果执行超时(10ms),则会重新放到M0对应的本地队列中。一直到执行到exit或者panic为止。

图片

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

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

相关文章

美轮美奂,尽在眼前——Aerial for Mac 高清鸟瞰屏保程序

想要让您的 Mac 屏幕焕发别样风采?那么,Aerial for Mac 高清鸟瞰屏保程序一定不容错过。这款应用程序将为您带来最优质的高清鸟瞰视频壁纸,让您的屏幕焕发无限活力和美感。 Aerial for Mac 高清鸟瞰屏保程序是一款专为 Mac 设计的屏幕保护程…

2023-9-25 货仓选址

题目链接&#xff1a;货仓选址 #include <iostream> #include <algorithm>using namespace std;const int N 100010;int n; int a[N];int main() {cin >> n;for(int i 0; i < n; i ) cin >> a[i];sort(a, a n);int res 0;for(int i 0; i < …

翻译像机翻?4点教会你ChatGPT高质量翻译

如果完全靠自己的英文和中文水平&#xff0c;要达到这样的翻译速度和质量那是不太可能的&#xff0c;主要还是得益于ChatGPT的帮助&#xff0c;首先用GPT-4的API粗翻&#xff0c;再用ChatGPT Plus精翻。很多人都用过ChatGPT翻译&#xff0c;但翻译出来的结果比起Google翻译和De…

C#通过重写Panel改变边框颜色与宽度的方法

在C#中,Panel控件是一个容器控件,用于在窗体或用户控件中创建一个可用于容纳其他控件的面板。Panel提供了一种将相关控件组合在一起并进行布局的方式。以下是Panel控件的详细使用方法: 在窗体上放置 Panel 控件: 在 Visual Studio 的窗体设计器中,从工具箱中拖动并放置一…

离散小波变换(概念与应用)

目录 概念光伏功率预测中,如何用离散小波变换提取高频特征概念 为您简单地绘制一些示意图来描述离散小波变换的基本概念。但请注意,这只是一个简化的示意图,可能不能完全捕捉到所有的细节和特性。 首先,我将为您绘制一个简单的小波函数和尺度函数的图像。然后,我会提供一…

链表oj题1(Leetcode)——移除链表元素,反转链表,链表的中间节点,

链表OJ 一&#xff0c;移除链表元素1.1分析1.2代码 二&#xff0c;找到链表的中间节点2.1分析2.2代码 三&#xff0c;反转链表3.1分析3.2代码 四&#xff0c;找到链表中倒数第k个节点4.1分析4.2代码 一&#xff0c;移除链表元素 移除链表元素 1.1分析 这里的删除要分成两种…

Android Jetpack组件架构 :LiveData的使用和原理

Android Jetpack组件架构&#xff1a; LiveDate的使用和原理 导言 继Lifecycle组件之后我们接下来要介绍的就是LiveDate组件&#xff0c;所谓LiveDate字面意思上就是有声明的数据&#xff0c;当数据有改动时该组件可以感知到这个操作并将该事件通知到其观察者&#xff0c;这样…

屏幕分辨率dpi解析(adb 调试查看)

authordaisy.skye的博客_CSDN博客-嵌入式,Qt,Linux领域博主 ro.sf.lcd_density属性指定了这个机型使用的dpi是多少&#xff0c;dpi全称是dots per inch&#xff0c;对角线每英寸的像素点的个数。 密度 ldpi mdpi hdpi xhdpi xxhdpi 分辨率 240x320 320x480 480x800 7…

2023-9-25 耍杂技的牛

题目链接&#xff1a;耍杂技的牛 #include <iostream> #include <algorithm>using namespace std;typedef pair<int, int> PII;const int N 50010;int n; PII cow[N];int main() {cin >> n;for(int i 0; i < n; i ){int w, s;cin >> w >…

Android 10.0 系统开启和关闭黑白模式主题功能实现

1. 概述 在10.0的rom系统开发定制化中,在系统SystemUI的下拉状态栏中,产品开发功能需求要求添加黑白模式功能开关的功能,就是打开黑白模式,系统颜色就会变成黑白颜色, 关闭黑白模式开关系统就会变成彩色模式,所以就需要了解下系统是怎么设置黑白模式和彩色模式的,然后添…

接口自动化测试之Mock

【软件测试面试突击班】如何逼自己一周刷完软件测试八股文教程&#xff0c;刷完面试就稳了&#xff0c;你也可以当高薪软件测试工程师&#xff08;自动化测试&#xff09; 1.Mock实现原理和实现机制 在某些时候&#xff0c;后端在开发接口的时候&#xff0c;处理逻辑非常复杂&a…

Android跨进程通信:Binder机制原理

目录 1. Binder到底是什么&#xff1f; 2. 知识储备 2.1 进程空间划分 2.2 进程隔离 & 跨进程通信&#xff08; IPC &#xff09; 2.3 内存映射 2.3.1 作用 2.3.2 实现过程 2.3.3 特点 2.3.4 应用场景 2.3.5 实例讲解 ① 文件读 / 写操作 ② 跨进程通信 3. Bi…

C#中的(++)和(--)运算符

目录 背景: 的前加 效果展示:​ 的后加 效果展示 :​ 总结: 背景: 自增和自减运算符存在于C/C/C#/Java等高级语言中&#xff0c;它的作用是在运算结束前(前置自增自减运算符 )或后(后置自增自减运算符 )将 变量的值加(或减)1。 在C#中&#xff0c;和--是自增和自减运…

基于springboot小区疫情防控系统

博主主页&#xff1a;猫头鹰源码 博主简介&#xff1a;Java领域优质创作者、CSDN博客专家、公司架构师、全网粉丝5万、专注Java技术领域和毕业设计项目实战 主要内容&#xff1a;毕业设计(Javaweb项目|小程序等)、简历模板、学习资料、面试题库、技术咨询 文末联系获取 项目介绍…

前端Vue3+element-plus表单输入框实现Cron表达式校验

页面如下&#xff1a; 本来想手写正则表达式校验&#xff0c;结果发现很麻烦&#xff0c;cron表达式组成如下&#xff1a; 开发使用框架为vue3element-plus&#xff0c;于是选择cron-validator依赖。使用步骤如下&#xff1a; 1、通过npm install cron-validator命令安装&…

面经分享 | 某康安全开发工程师

本文由掌控安全学院 - sbhglqy 投稿 一、反射型XSS跟DOM型XSS的最大区别 DOM型xss和别的xss最大的区别就是它不经过服务器&#xff0c;仅仅是通过网页本身的JavaScript进行渲染触发的。 二、Oracle数据库了解多吗 平常用的多的是MySQL数据库&#xff0c;像Oracle数据库也有…

想要精通算法和SQL的成长之路 - 最长回文子序列

想要精通算法和SQL的成长之路 - 最长回文子序列 前言一. 最长回文子序列 前言 想要精通算法和SQL的成长之路 - 系列导航 一. 最长回文子序列 原题链接 首先&#xff0c;我们看下动态规划方程的定义&#xff0c;我们用dp[i][j] 来代表&#xff1a;字符串s在下标区间为[i,j]之间…

Unity入门教程(上)

七、运行游戏 再次保存我们的项目文件&#xff08;返回步骤四&#xff09;。保存完成后&#xff0c;让我们把游戏运行起来。 1&#xff0c;确认游戏视图标签页右上方的Maximize on Play图标处于按下状态&#xff0c;然后点击画面上方的播放按钮&#xff08;位于工具栏中间的播…

网络竞品分析:用爬虫技术洞悉竞争对手

概述 网络竞品分析是指通过互联网收集、分析和比较竞争对手的信息&#xff0c;以了解他们的优势和劣势&#xff0c;找出自己的差距和机会&#xff0c;制定有效的竞争策略。网络竞品分析涉及的信息包括竞争对手的产品、价格、渠道、营销、用户反馈等方面。爬虫技术是一种自动化…

看板系统如何异地电脑手机访问?主机内网ip端口映射域名外网访问

看板系统是一种可视化管理系统平台&#xff0c;如生产管理看板、项目管理看板、APP运营看板等将企业或工厂本地项目具体数据转换成图表模式&#xff0c;方便实时管理和汇总&#xff0c;有效提升工作效率和助力生产实践。 单位内部服务器部署了看板管理系统&#xff0c;由于无公…