Go 原理之 GMP 并发调度模型
一、Go 的协程 goroutine
go 的特性:协程(goroutine),goroutine 是 go 自己实现的、为了解决线程的性能问题,goroutine 协程是用户态的,由 go runtime 创建和销毁,没有内核消耗,线程是内核态的,与操作系统相关,创建和销毁成本较高。
goroutine 提高 cpu 的利用率,解决了高消耗的CPU调度,用户态的轻量级的线程,约4k
这也是 go 为什么性能那么好的原因,而 go 实现 goroutine 协程的原理:GMP 调度模型
二、GMP 调度模型

G:
goroutine 协程,go runtime 包自己实现的一种数据结构,存储执行时唯一的内存栈信息
P:
processor 处理器,go runtime 包实现的调度器,主要用来并发调度 goroutine 协程的启动、执行、等待、暂停、销毁等生命周期
M:
thread 线程,就是我们平时理解的线程,如果你不理解什么是线程,请参考文章 进程线程协程的概念和区别
那么 go 的协程 goroutine 是如何实现以及调度执行的过程是什么样子的呢?
比如我们使用 go 开启一个协程:
go func(){
// 开启协程,处理逻辑
}()
'go func()'经历了哪些过程
- 通过 'go func()' 创建一个 goroutine(数据结构,有个唯一 gid,以及内存栈信息),这里称为:G
- 有两个存储 G 的队列(本地P队列,全局队列),新创建的 G 会优先加入本地 P 队列中,如果满了就会保存在全局队列中
- G 最终会通过 P 调度运行在 M 中,MP 是组合(一个M必须持有一个P,M:P=1:1)
M 会从 P 的队列中弹出一个可执行的 G 来执行,如果没有,则会从全局队列中获取,
全局队列也没有,则会从其他 MP 队列中偷取一个 G执行,从其他 P 偷的方式称为 work stealing
- 一个 M 调度 G 执行是一个循环过程
- 当 M 执行 G 过程中发生 systemCall 阻塞,M 会阻塞,如果当前有一些 G 在执行,runtime 会把这个线程 M 从 P 中摘除(detach),此时 P 会和 M 解绑即 hand off,然后再创建/从休眠队列中取一个 M 来服务这个 P
系统调用(如文件IO)阻塞(同步):阻塞MG,M与P解绑
网络 IO 调用阻塞(异步):G 移动到NetPoller,M 继续执行 P 中的 G
mutex/chan阻塞(异步):G 移动到 chan 的等待队列中,M 继续执行 P 中的 G
- 当 M 系统调用结束后进入休眠/销毁状态,这个 G 会尝试获取一个空闲的 P 执行,如果没有,这个 G 会放入全局队列


M 每隔约 10ms 会切换一个 G,被切换的 G 会重新回到本地P队列
如果在 Goroutine 去执行一个 sleep 操作,导致 M 被阻塞了。Go 程序后台有一个监控线程 sysmon,它监控那些长时间运行的 G 任务然后设置可以强占的标识符,别的 Goroutine 就可以抢先进来执行。
三、M0 & G0 的启动
go 启动的时候,默认会启动 M0 线程 和 G0 协程
M0:编号为 0 的主线程
GO:编号为 0 的主协程

四、 协程 goroutine 的调度策略
队列轮转:
P 会周期性的将G调度到M中执行,执行一段时间后,保存上下文,将 G 放到队列尾部,然后从队列中再取出一个G进行调度
除此之外,P还会周期性的查看全局队列是否有G等待调度到M中执行
系统调度:
当 G0 即将进入系统调用时,M0 将释放 P,进而某个空闲的 M1 获取 P,继续执行 P 队列中剩下的 G。
M1 的来源有可能是 M 的缓存池,也可能是新建的
当 G0 系统调用结束后,如果有空闲的P,则获取一个P,继续执行 G0。如果没有,则将 G0 放入全局队列,等待被其他的 P 调度。然后 M0 将进入缓存池睡眠
抢占式调度:
sysmon 监控协程,如果 g 运行时间过长
10 ms,那会发送信号给到 m,g 会被挂起,m继续执行 p 中的 g,防止其他 g 被饿死
五、协程的生命周期
创建、等待(调用 gopark 进入等待状态)、唤醒执行(调用 goready 唤醒等待的 g 执行)、销毁
五、GMP 的数量
G 的初始化大小是 2-4 k,具体数量由内存决定,
P 的数量由用户设置的 GoMAXPROCS 决定,等于CPU的核心数,但是不论 GoMAXPROCS 设置为多大,P 的储存G的数量最大为 256
M 默认限制 10000
常见问题
1. Golang 为什么要创建 goroutine 协程
轻量:1.大小只有 2-4 k,用户级线程,减少了内核态切换创建的开销
操作系统中虽然已经有了多线程、多进程来解决高并发的问题,但是在当今互联网海量高并发场景下,对性能的要求也越来越苛刻,大量的进程/线程会出现内存占用高、CPU消耗多的问题,很多服务的改造与重构也是为了降本增效。
一个进程可以关联多个线程,线程之间会共享进程的一些资源,比如内存地址空间、打开的文件、进程基础信息等,每个线程也都会有自己的栈以及寄存器信息等,线程相比进程更加轻量,而协程相对线程更加轻量,多个协程会关联到一个线程,协程之间会共享线程的一些信息,每个协程也会有自己的栈空间,所以也会更加轻量级。从进程到线程再到协程,其实是一个不断共享,减少切换成本的过程。
Golang 使用协程主要有以下几个原因:
● 大小
协程大概是2-4k,线程大概是1m
● 创建、切换和销毁
协程是用户态的,由 runtime 创建和销毁,没有内核消耗,线程是内核态的,与操作系统相关,创建和销毁成本较高
2. 什么是 CSP 并发模型?
CSP 并发模型:不要以共享内存的方式来通信,而以通信的方式来共享内存
go 实现 CSP 并发模式是通过: goroutine + chan
3. G 调度执行中断是如何恢复的?
G 是一个数据结构,存储上下文堆栈信息
中断的时候将寄存器里的栈信息,保存到自己的 G 对象(sudog)里面。当再次轮到自己执行时,将自己保存的栈信息复制到寄存器里面,这样就接着上次之后运行了。
4. 当 G 阻塞时,g、m、p 会发生什么
G 的状态会从运行态变为阻塞态,放入 P 等待队列
M 会从该 Goroutine 所在的 P 中分离出来,转而执行其他 Goroutine
P 会从该 Goroutine 所在的 M 中分离出来,将该 Goroutine 放入等待队列中,并从空闲的 M 队列中取出一个 M,将其绑定到该 P 上
5. runtime 是什么?
golang 底层的基础设施:
GPM 的创建和调度
内存分配
GC
内置函数如 map,chan,slice,反射的实现等
pprof,trace,CGO
操作系统以及 CPU 的一些封装
....
基本上就是 go 的底层所在了

6. 怎么启动第一个 goroutine?
main 启动函数会默认启动 G0 协程
7. Go 的协程为什么那么好,与线程的区别
● 大小
协程大概是 2k-4k,线程大概是1m
● 创建、切换和销毁
协程是用户态的,由runtime创建和销毁,没有内核消耗,线程是内核态的,与操作系统相关,创建和销毁成本较高
提高cpu的利用率,解决了高消耗的CPU调度,用户态的轻量级的线程,约4k
减少了内核切换成本,操作系统分为用户态和内核态(表示操作系统底层)

8. 线程与协程的区别
一个线程有多个协程,协程是用户态的轻量级的线程,非抢占式的,由用户控制,没有内核切换的开销
原文地址
Go 原理之 GMP 并发调度模型的更多相关文章
- Goroutine并发调度模型深度解析之手撸一个协程池
golanggoroutine协程池Groutine Pool高并发 并发(并行),一直以来都是一个编程语言里的核心主题之一,也是被开发者关注最多的话题:Go语言作为一个出道以来就自带 『高并发』光环 ...
- go并发调度原理学习
aaarticlea/jpeg;base64,/9j/4AAQSkZJRgABAQAAkACQAAD/4QB0RXhpZgAATU0AKgAAAAgABAEaAAUAAAABAAAAPgEbAAUAA
- 图解协程调度模型-GMP模型
现在无论是客户端.服务端或web开发都会涉及到多线程的概念.那么大家也知道,线程是操作系统能够进行运算调度的最小单位,同一个进程中的多个线程都共享这个进程的全部系统资源. 线程 三个基本概念 内核线程 ...
- 重新梳理调度器——GMP 调度模型
调度器--GMP 调度模型 Goroutine 调度器,它是负责在工作线程上分发准备运行的 goroutines. 首先在讲 GMP 调度模型之前,我们先了解为什么会有这个模型,之前的调度模型是什么样 ...
- [golang]Golang实现高并发的调度模型---MPG模式
Golang实现高并发的调度模型---MPG模式 传统的并发形式:多线程共享内存,这也是Java.C#或者C++等语言中的多线程开发的常规方法,其实golang语言也支持这种传统模式,另外一种是Go语 ...
- C++11并发内存模型学习
C++11标准已发布多年,编译器支持也逐渐完善,例如ms平台上从vc2008 tr1到vc2013.新标准对C++改进体现在三方面:1.语言特性(auto,右值,lambda,foreach):2.标 ...
- OS中处理机调度模型和调度算法
OS中处理机调度模型和调度算法 调度层次 1.1. 高级调度(长程调度,作业调度) 功能:依据某种算法.把在外存队列上处于后备队列的那些作业调入内存.以作业为操做对象. 作业:比程序更为广泛的概念,不 ...
- 4、Java并发性和多线程-并发编程模型
以下内容转自http://ifeve.com/%E5%B9%B6%E5%8F%91%E7%BC%96%E7%A8%8B%E6%A8%A1%E5%9E%8B/: 并发系统可以采用多种并发编程模型来实现. ...
- word2vec原理(一) CBOW与Skip-Gram模型基础
word2vec原理(一) CBOW与Skip-Gram模型基础 word2vec原理(二) 基于Hierarchical Softmax的模型 word2vec原理(三) 基于Negative Sa ...
- Java并发编程的艺术读书笔记(2)-并发编程模型
title: Java并发编程的艺术读书笔记(2)-并发编程模型 date: 2017-05-05 23:37:20 tags: ['多线程','并发'] categories: 读书笔记 --- 1 ...
随机推荐
- Vite 3 来了!新增功能 + 如何迁移
@charset "UTF-8"; .markdown-body { line-height: 1.75; font-weight: 400; font-size: 15px; o ...
- C#之清除已经注册的事件
private static void DealA(object sender, UnhandledExceptionEventArgs e) { Console.WriteLine($"E ...
- 详解Git中的.gitignore文件
1.什么是.gitignore文件?有什么作用? 在Git中,有一种特殊的文件,其文件全名就是 .gitignore,这个文件可以用txt打开,主要功能是屏蔽某些文件,使得这些文件不被追踪(track ...
- 5 easybr指纹浏览器内存修改教程
目的 navigator.deviceMemory可以暴露设备的物理内存和运行状态,被用于设备唯一性识别或判断设备等级. 通过伪造这类信息,可以增强防关联.防追踪能力. easybr指纹浏览器提供演示 ...
- css——慕课
- 玩转代码:深入GitHub,高效管理我们的“shou学”平台源代码
玩转代码:深入GitHub,高效管理我们的"shou学"平台源代码 在当今快节奏的开发世界中,有效地管理代码不仅仅是一种良好实践,更是一种必需.无论您是独立开发者还是大型团队的一员 ...
- React Native开发鸿蒙Next---RN键盘问题
.markdown-body { line-height: 1.75; font-weight: 400; font-size: 16px; overflow-x: hidden; color: rg ...
- pytorch入门 - 微调huggingface大模型
在自然语言处理(NLP)领域,预训练语言模型如BERT已经成为主流.HuggingFace提供的Transformers库让我们能够方便地使用这些强大的模型. 本文将详细介绍如何使用PyTorch微调 ...
- .NET 10 支持Linux 的Shebang(Hashbang)
.NET 10 Preview 5 带来的C# 文件脚本化运行,在 Linux/Unix 系统中通过 #!/usr/bin/dotnet run 支持 Shebang(Hashbang) 的详细说明: ...
- 最值得关注的2025年公众号编辑器Top 10,提升你的内容质量
公众号编辑器全面指南:10款主流工具深度评测 前言 公众号编辑器作为新媒体和自媒体人日常必备的工具,能极大提升公众号运营效率和内容质量.面对众多类型和风格各异的公众号编辑器网站,如何选择一款真正适合自 ...