GoLang 的协程调度和 GMP 模型

GoLang 是怎么启动的

关于 GoLang 的汇编语言,请查阅 参考文献[1]参考文献[2]

  1. 编写一个简单的 GoLang 程序 main.go, 用 go build -o main main.go 编译生成可执行文件 main

    package main
    import "fmt"
    func main(){
    fmt.Println("Hello, World!")
    }
  2. 使用 gdb main 命令调试 main 程序

  3. 输入 info files 命令查看程序入口地址

  4. break 命令给该地址打断点,然后运行程序。发现程序在 _rt0_amd64_linux 函数的 JMP _rt0_amd64(SB) 命令停下。

  5. _rt0_amd64_linux 就是入口函数,该函数没有做任何操作直接跳转到 _rt0_amd64(SB),见 参考文献[6]

  6. 继续跳转到 _rt0_amd64, 可以看到该函数将存放在栈内的 argcargv复制给参数寄存器 DISI 后又跳转到 runtime·rt0_go(SB)

  7. 参考文献[8] 中我们可以看到 GoLang 跑起来的主要代码:

    (1) 使用 schedinit 始化堆栈,GC 等,根据 GOMAXPROCS 设置创建 P 结构的池子。参考文献[10]
    (2) 创建一个新的 G,来执行 runtime.mainPC 函数
    (3) 启动当前的 M, mstart 调用 schedule,开始执行之前绑定的 mainPC 所在的 G

GoLang 的 GMP 模型

上一节提到来 GMP 到底是什么意思呢?

  • P(Processor): “处理器”,主要用来限制实际运行的 M 的数量。默认数量跟 CPU 的物理线程数一致,受 GOMAXPROCS 控制。 参考文献[15]

  • M(Machine): OS Thread,由 OS 调度。M 的数量不一定。但是处于非阻塞状态的 MP 决定。

    MP 的区别与联系在于:P 是 GoLang 假想的“处理器”,控制实际能够跑起来的 M 的数量。

    如,在 G 的数量无限多的情况下,一开始 MP 的数量一样多。但是当运行在 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 就会优先调度和执行该 PLRQ 上的 G

    需要重点关注的一点是:M 才是 OS Thread,因此只有 M 才有执行和调度 G 的能力。而 P 只是一个约束条件。

  • GRQ(Global Run Queue): 没有绑定任何 PG 就会被扔进 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 (GOMAXPROCSshrank). We reuse Ps if GOMAXPROCS 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 的协作

  1. 一般调度:如图,当 M 绑定 P 时,就会开始调度执行 PLRQ 中的 G

    当执行 61 ticks,或者 LRQ 没有 G 时,就会去 GRQ 或者其他的 PLRQG 来执行。

    需要特别注意的是图中 LRQGMPCore 的相对位置关系。M 需要绑定 P 才能调度执行 G, 因此 G 在它俩之间。

  2. 异步系统调用:当 G 需要执行异步系统调用时,M 会把它扔给 NetPoller 管理,然后从 LRQ 中重新取一个 G 来处理。

    NetPoller 通过 epoll 等函数,由监控线程 sysmon 定期询问。当某个 G 的异步系统调用完成后,该 G 就会被扔进 GRQ 中,等待被再次调度。

  3. 同步系统调用:我们知道,同步系统调用阻塞了 M(OS Thread), 该 M 是没有办法为其他 G 提供服务的。此时,该 MP 而言就是没有意义的。(P 的意义是约束运行中的 M 的个数)

    所以,GoLang 会将阻塞的 M - GP 解绑,然后给 P 创建/调度一个新的 M。这样就能保证“运行中的 M 的数量等于 P 的总数”。

    当同步系统调用完成后,阻塞的 M 会将执行系统调用的 G 还回 LRQ。然后该 M 等待销毁或者被重新绑定给某个 P

几个重要的函数

  1. 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 或者其他 PLRQG 来调度执行
    (7)如果都没有找到 G,当前 M 让出占用的 P, 进入休眠状态

  2. runtime.mainPC(runtime.main) 参考文献[20]

    (1)限制最大栈大小: Max stack size is 1 GB on 64-bit, 250 MB on 32-bit
    (2)创建一个不需要绑定 PM,,执行 sysmon 函数
    (3)创建 GC goroutine,启动 GC
    (4)运行 package mainmain 函数
    (5)一系列的收尾操作

  3. runtime.sysmon 参考文献[21]
    (1)获取 NetPoller 中已完成操作的 G,将其加入 GRQ
    (2)retake

    • 抢占长时间运行的 G
    • 回收被 syscall 长时间阻塞的 P

参考文献

GoLang 的协程调度和 GMP 模型的更多相关文章

  1. Golang 的 协程调度机制 与 GOMAXPROCS 性能调优

    作者:林冠宏 / 指尖下的幽灵 掘金:https://juejin.im/user/587f0dfe128fe100570ce2d8 博客:http://www.cnblogs.com/linguan ...

  2. Golang 协程调度

    一.线程模型 N:1模型,N个用户空间线程在1个内核空间线程上运行.优势是上下文切换非常快但是无法利用多核系统的优点. 1:1模型,1个内核空间线程运行一个用户空间线程.这种充分利用了多核系统的优势但 ...

  3. 图解协程调度模型-GMP模型

    现在无论是客户端.服务端或web开发都会涉及到多线程的概念.那么大家也知道,线程是操作系统能够进行运算调度的最小单位,同一个进程中的多个线程都共享这个进程的全部系统资源. 线程 三个基本概念 内核线程 ...

  4. GO GMP协程调度实现原理 5w字长文史上最全

    1 Runtime简介 Go语言是互联网时代的C,因为其语法简洁易学,对高并发拥有语言级别的亲和性.而且不同于虚拟机的方案.Go通过在编译时嵌入平台相关的系统指令可直接编译为对应平台的机器码,同时嵌入 ...

  5. skynet源码阅读<5>--协程调度模型

    注:为方便理解,本文贴出的代码部分经过了缩减或展开,与实际skynet代码可能会有所出入.    作为一个skynet actor,在启动脚本被加载的过程中,总是要调用skynet.start和sky ...

  6. 图解Go协程调度原理,小白都能理解

    阅读本文仅需五分钟,golang协程调度原理,小白也能看懂,超实用. 什么是协程 对于进程.线程,都是有内核进行调度,有CPU时间片的概念,进行抢占式调度.协程,又称微线程,纤程.英文名Corouti ...

  7. GoLang之协程

    GoLang之协程 目前,WebServer几种主流的并发模型: 多线程,每个线程一次处理一个请求,在当前请求处理完成之前不会接收其它请求:但在高并发环境下,多线程的开销比较大: 基于回调的异步IO, ...

  8. 在C++中使用golang的协程

    开源项目cpp_features提供了一个仿golang协程的stackful协程库. 可以在c++中使用golang的协程,大概语法是这样的: #include <iostream> v ...

  9. Golang 之协程详解

    转自:https://www.cnblogs.com/liang1101/p/7285955.html 一.Golang 线程和协程的区别 备注:需要区分进程.线程(内核级线程).协程(用户级线程)三 ...

  10. Python开发——15.协程与I/O模型

    一.协程(Coroutine) 1.知识背景 协程又称微线程,是一种用户态的轻量级线程.子程序,或者称为函数,在所有语言中都是层级调用,比如A调用B,B在执行过程中又调用了C,C执行完毕返回,B执行完 ...

随机推荐

  1. 斐波那契散列算法和hashMap实践

    斐波那契散列和hashMap实践 适合的场景:抽奖(游戏.轮盘.活动促销等等) 如果有不对的地方,欢迎指正! HashMap实现数据散列: 配置项目,引入pom.xml: <dependency ...

  2. JS逆向实战9——cookies DES加密混淆

    cookie加密 DES 混淆 目标网站:aHR0cHM6Ly90bGNoZW1zaG9wLnlvdXpoaWNhaS5jb20vbWFpbi90ZW5kP05vdGljZUNhdGVJZD0xJk5 ...

  3. 浏览器DevTools使用技巧

    我们是袋鼠云数栈 UED 团队,致力于打造优秀的一站式数据中台产品.我们始终保持工匠精神,探索前端道路,为社区积累并传播经验价值. 本文作者:正则 作为一名前端开发人员,平时开发中使用最多的就是 Ch ...

  4. Vue3 企业级优雅实战 - 组件库框架 - 8 搭建组件库 cli

    前面的文章分享了组件库的开发.example.组件库文档,本文分享组件库 cli 开发. 1 为什么要开发组件库 cli 回顾一个新组件的完整开发步骤: 1 在 packages 目录下创建组件目录 ...

  5. flutter系列之:移动端手势的具体使用

    目录 简介 赋予widget可以点击的功能 会动的组件 可删除的组件 总结 简介 之前我们介绍了GestureDetector的定义和其提供的一些基本的方法,GestureDetector的好处就是可 ...

  6. 解决MVVMLight导航VM不重置问题

    问题阐述:使用MVVMLight导航发现导航后VM里面的数据并未进行重置,需要界面跳转后,历史VM也进行销毁重置,并释放 解决办法: 方法一:在当前界面进行Unloaded进行VM注销并进行重新注入代 ...

  7. 10、RestTemplate方式实现远程调用Client

    一.JSONObject类详解: JSONobject是FastJson提供的对象,在API中是用一个私有的常量map进行封装的,实际就是一个map,只不过 FastJson对其进行了封装,添加了很多 ...

  8. 2、postman调试

    Postman接口调试: postman博客参考 Postman是一个API(接口)开发协作平台,其提供了发送请求.检查响应.自动化测试.数据模拟.服务监控.文档分享等一系列与API(接口)开发有关的 ...

  9. 常用 Git 命令行操作

    本文记录了一些常用 Git 命令行操作的具体使用方式 git clone git clone REPOSITORY_URL 拉取仓库,并使用仓库名作为本地文件名 git clone REPOSITOR ...

  10. [常用工具] Caffe ssd常见问题集合

    1 Check failed: a <= b <0 vs -1.19209e-007> 网上办法是注释掉 CHECK_LE(a, b),但是这样会出大问题.解决办法见2. 如果注释掉 ...