GoLang 的协程调度和 GMP 模型
GoLang 的协程调度和 GMP 模型
GoLang 是怎么启动的
编写一个简单的 GoLang 程序 main.go, 用
go build -o main main.go
编译生成可执行文件 mainpackage main
import "fmt"
func main(){
fmt.Println("Hello, World!")
}
使用
gdb main
命令调试 main 程序输入
info files
命令查看程序入口地址
用
break
命令给该地址打断点,然后运行程序。发现程序在_rt0_amd64_linux
函数的JMP _rt0_amd64(SB)
命令停下。
_rt0_amd64_linux
就是入口函数,该函数没有做任何操作直接跳转到_rt0_amd64(SB)
,见 参考文献[6]
继续跳转到
_rt0_amd64
, 可以看到该函数将存放在栈内的argc
和argv
复制给参数寄存器 DI 和 SI 后又跳转到runtime·rt0_go(SB)
在 参考文献[8] 中我们可以看到 GoLang 跑起来的主要代码:
(1) 使用schedinit
始化堆栈,GC 等,根据GOMAXPROCS
设置创建 P 结构的池子。参考文献[10]
(2) 创建一个新的 G,来执行runtime.mainPC
函数
(3) 启动当前的 M,mstart
调用schedule
,开始执行之前绑定的mainPC
所在的 G
GoLang 的 GMP 模型
上一节提到来 G,M, P 到底是什么意思呢?
P(Processor): “处理器”,主要用来限制实际运行的 M 的数量。默认数量跟 CPU 的物理线程数一致,受
GOMAXPROCS
控制。 参考文献[15]M(Machine): OS Thread,由 OS 调度。M 的数量不一定。但是处于非阻塞状态的 M 由 P 决定。
M 和 P 的区别与联系在于:P 是 GoLang 假想的“处理器”,控制实际能够跑起来的 M 的数量。
如,在 G 的数量无限多的情况下,一开始 M 和 P 的数量一样多。但是当运行在 M 上的 G 调用了同步系统调用阻塞了 M, 此时这个 M 线程是没办法做其他操作的。
但是 GoLang 想要运行中的 M(OS Thread) 数量是和 P 的数量一样多的,所以它会再创建一个 M(OS Thread) 来跑新的 G。这样就能动态的保证“运行中的 M 的数量等于 P 的总数”
G(GoRoutine):协程,应用层看到的“线程”。由 M 调度和执行。
LRQ(Local Run Queue): 从定义可知,P 相当于是对 M 的约束,M 只有绑定来 P 才能实际调度和执行 G。
因此,GoLang 给每个 P 设置了一个 LRQ 来维护一个待执行 G 的队列。
当有 M 绑定在 P 上时,M 就会优先调度和执行该 P 的 LRQ 上的 G
需要重点关注的一点是:M 才是 OS Thread,因此只有 M 才有执行和调度 G 的能力。而 P 只是一个约束条件。
GRQ(Global Run Queue): 没有绑定任何 P 的 G 就会被扔进 GRQ,等待被需要的 P-M 加入 LRQ 执行。
P 的状态:
- Pidle: means a P is not being used to run user code or the scheduler. Typically, it’s on the idle P list and available to the scheduler
- Prunning: means a P is owned by an M and is being used to run user code or the scheduler.
- Psyscall: means a P is not running user code, may be stolen by another M.
- Pgcstop: means a P is halted for STW and owned by the M that stopped the world.
- Pdead: means a P is no longer used (
GOMAXPROCS
shrank). We reuseP
s ifGOMAXPROCS
increases.G 的状态:
- Gidle:just allocated and has not yet been initialized
- Grunnable:this goroutine is on a run queue
- Grunning:means this goroutine may execute user code
- Gsyscall: means this goroutine is executing a system call
- Gwaiting: means this goroutine is blocked in the runtime(channel)
GMP 的协作
一般调度:如图,当 M 绑定 P 时,就会开始调度执行 P 的 LRQ 中的 G。
当执行 61 ticks,或者 LRQ 没有 G 时,就会去 GRQ 或者其他的 P 的 LRQ 抢 G 来执行。
需要特别注意的是图中 LRQ,G,M,P 和 Core 的相对位置关系。M 需要绑定 P 才能调度执行 G, 因此 G 在它俩之间。
异步系统调用:当 G 需要执行异步系统调用时,M 会把它扔给 NetPoller 管理,然后从 LRQ 中重新取一个 G 来处理。
NetPoller 通过
epoll
等函数,由监控线程sysmon
定期询问。当某个 G 的异步系统调用完成后,该 G 就会被扔进 GRQ 中,等待被再次调度。
同步系统调用:我们知道,同步系统调用阻塞了 M(OS Thread), 该 M 是没有办法为其他 G 提供服务的。此时,该 M 对 P 而言就是没有意义的。(P 的意义是约束运行中的 M 的个数)
所以,GoLang 会将阻塞的 M - G 和 P 解绑,然后给 P 创建/调度一个新的 M。这样就能保证“运行中的 M 的数量等于 P 的总数”。
当同步系统调用完成后,阻塞的 M 会将执行系统调用的 G 还回 LRQ。然后该 M 等待销毁或者被重新绑定给某个 P。
几个重要的函数
runtime.schedule 参考文献 [24]
(1)从 TLS 获取当前正在运行的 G 的信息
(2)M 是否绑定到当前的 G(同步系统调用)?M 让出绑定的 P ,等待同步系统调用的 G 结束
(3)GC?STW(stop the word) for GC
(4)当前 P 每执行 61 ticks,从 GRQ 取一定量的 G 加入 LRQ 中
(5)从 LRQ 获取可执行的 G
(6)如果当前的 LRQ 没有 G, 则从 GRQ 或者其他 P 的 LRQ 抢 G 来调度执行
(7)如果都没有找到 G,当前 M 让出占用的 P, 进入休眠状态runtime.mainPC(runtime.main) 参考文献[20]
(1)限制最大栈大小: Max stack size is 1 GB on 64-bit, 250 MB on 32-bit
(2)创建一个不需要绑定 P 的 M,,执行sysmon
函数
(3)创建 GC goroutine,启动 GC
(4)运行package main
的main
函数
(5)一系列的收尾操作runtime.sysmon 参考文献[21]
(1)获取 NetPoller 中已完成操作的 G,将其加入 GRQ 中
(2)retake:- 抢占长时间运行的 G
- 回收被
syscall
长时间阻塞的 P
参考文献
- [1] A Quick Guide to Go’s Assembler
- [2] Lecture 4: x86_64 Assembly Language
- [3] GDB info files command
- [4] 探索golang程序启动过程
- [5] Compile packages and dependencies
- [6] _rt0_amd64_linux function
- [7] _rt0_amd64 function
- [8] runtime·rt0_go function
- [9] Notes on the Go 1.4 Run-Time
- [10] runtime.schedinit funtion
- [11] runtime.newproc funtion
- [12] runtime.mstart funtion
- [13] runtime.main funtion
- [14] the definition of G, M, P
- [15] Concurrency, Goroutines and GOMAXPROCS
- [16] The Go scheduler
- [17] Scheduling In Go : Part II - Go Scheduler
- [18] Thread-local storage - Wikipedia
- [19] findrunnable function
- [20] runtime.mainPC
- [21] runtime.sysmon
- [22] runtime.retake
- [23] runtime.preemptone
- [24] runtime.schedule
GoLang 的协程调度和 GMP 模型的更多相关文章
- Golang 的 协程调度机制 与 GOMAXPROCS 性能调优
作者:林冠宏 / 指尖下的幽灵 掘金:https://juejin.im/user/587f0dfe128fe100570ce2d8 博客:http://www.cnblogs.com/linguan ...
- Golang 协程调度
一.线程模型 N:1模型,N个用户空间线程在1个内核空间线程上运行.优势是上下文切换非常快但是无法利用多核系统的优点. 1:1模型,1个内核空间线程运行一个用户空间线程.这种充分利用了多核系统的优势但 ...
- 图解协程调度模型-GMP模型
现在无论是客户端.服务端或web开发都会涉及到多线程的概念.那么大家也知道,线程是操作系统能够进行运算调度的最小单位,同一个进程中的多个线程都共享这个进程的全部系统资源. 线程 三个基本概念 内核线程 ...
- GO GMP协程调度实现原理 5w字长文史上最全
1 Runtime简介 Go语言是互联网时代的C,因为其语法简洁易学,对高并发拥有语言级别的亲和性.而且不同于虚拟机的方案.Go通过在编译时嵌入平台相关的系统指令可直接编译为对应平台的机器码,同时嵌入 ...
- skynet源码阅读<5>--协程调度模型
注:为方便理解,本文贴出的代码部分经过了缩减或展开,与实际skynet代码可能会有所出入. 作为一个skynet actor,在启动脚本被加载的过程中,总是要调用skynet.start和sky ...
- 图解Go协程调度原理,小白都能理解
阅读本文仅需五分钟,golang协程调度原理,小白也能看懂,超实用. 什么是协程 对于进程.线程,都是有内核进行调度,有CPU时间片的概念,进行抢占式调度.协程,又称微线程,纤程.英文名Corouti ...
- GoLang之协程
GoLang之协程 目前,WebServer几种主流的并发模型: 多线程,每个线程一次处理一个请求,在当前请求处理完成之前不会接收其它请求:但在高并发环境下,多线程的开销比较大: 基于回调的异步IO, ...
- 在C++中使用golang的协程
开源项目cpp_features提供了一个仿golang协程的stackful协程库. 可以在c++中使用golang的协程,大概语法是这样的: #include <iostream> v ...
- Golang 之协程详解
转自:https://www.cnblogs.com/liang1101/p/7285955.html 一.Golang 线程和协程的区别 备注:需要区分进程.线程(内核级线程).协程(用户级线程)三 ...
- Python开发——15.协程与I/O模型
一.协程(Coroutine) 1.知识背景 协程又称微线程,是一种用户态的轻量级线程.子程序,或者称为函数,在所有语言中都是层级调用,比如A调用B,B在执行过程中又调用了C,C执行完毕返回,B执行完 ...
随机推荐
- 启动homestead虚拟机 vagrant up执行后,提示Timed out while waiting for the machine to boot
最近在启动homestead虚拟机时,总会卡在ssh验证这,几分钟后,就报timed out-- 以往都是重启电脑后,再次执行vagrant up后就能正常启动. 今日重启电脑很多次也无用. 查询解决 ...
- PGL图学习之基于GNN模型新冠疫苗任务[系列九]
PGL图学习之基于GNN模型新冠疫苗任务[系列九] 项目链接:https://aistudio.baidu.com/aistudio/projectdetail/5123296?contributio ...
- apache文件工具类的使用:org.apache.commons.io.FileUtils
说明 org.apache.commons.io.FileUtils 工具类包含了许多操作文件的方法,此文章介绍一些常用的文件操作方法,方便使用的时候查阅参考 创建输入流 public static ...
- 一文带你搞懂 Google 发布的新开源项目 GUAC
随着软件供应链攻击的显著增加,以及 Log4j 漏洞带来的灾难性后果和影响,软件供应链面临的风险已经成为网络安全生态系统共同关注的最重要话题之一.根据业内权威机构 Sonatype 发布的2022软件 ...
- IdentityServer4的最佳使用
简介 本人做微服务项目也有一段时间了,在微服务中让我感触颇深的就是这个IdentityServer4了,ID4(IdentityServer4的简称)中涉及的概念颇多,本文不谈概念(我怕读者没耐心 ...
- python 之集合(set)
集合是一个无序的,不允许重复的元素列表,根据这个特性,可以利用集合对列表进行去重操作 集合创建 # 集合中不能含list.dict set2 = {"rice", 1, (True ...
- Vue前后端分离实现登录的一个简单demo
1.建立一个Maven项目,并添加Spring相关依赖 2.编写Controller类相应的接口和配置类 LoginController类,编写接口的业务逻辑 package com.springbo ...
- Hive详解(01) - 概念
Hive详解(01) - 概念 hive简介 Hive:由Facebook开源用于解决海量结构化日志的数据统计工具,是基于Hadoop的一个数据仓库工具,可以将结构化的数据文件映射为一张表,并提供类S ...
- python进阶之路2——解释器软件安装
内容概要 计算机五大组成部分 计算机三大核心硬件 操作系统 编程与编程语言 编程语言发展史 编程语言的分类 python解释器下载与安装 python解释器多版本共存 pycharm安装 计算机五大组 ...
- [数据结构]广度优先搜索算法(Breadth-First-Search,BFS)
广度优先搜索的概念 广度优先搜索(BFS)类似于二叉树的层序遍历算法,它的基本思想是:首先访问起始顶点v,然后由v出发,依次访问v的各个未被访问过的邻接顶点w1,w2,w3-.wn,然后再依次访问w1 ...