Golang 的 协程调度机制 与 GOMAXPROCS 性能调优
作者:林冠宏 / 指尖下的幽灵
GitHub : https://github.com/af913337456/
腾讯云专栏: https://cloud.tencent.com/developer/user/1148436/activities
前序
正确地认识 G , M , P 三者的关系,能够对协程的调度机制有更深入的理解! 本文将会完整介绍完 go 协程的调度机制,包含:
- 调度对象的主要组成
- 各对象的关系 与 分工
- gorutine 协程是如何被执行的
- 内核线程 sysmon 对 gorutine 的管理
- gorutine 协程中断挂起 与 恢复
- GOMAXPROCS 如何影响 go 的并发性能
目录
调度器的三个基本对象:
Golang 简称 Go,Go 的协程(goroutine) 和我们常见的线程(Thread)一样,拥有其调度器。
- G (Goroutine),代表协程,也就是每次代码中使用
go 关键词时候会创建的一个对象 - M (Work Thread),工作线程
- P (Processor),代表一个
处理器,又称上下文
G-M-P三者的关系与特点:
- 每一个运行的 M 都必须绑定一个 P,线程M 创建后会去检查并执行G (goroutine)对象
- 每一个 P 保存着一个协程G 的
队列 - 除了每个 P 自身保存的 G 的队列外,调度器还拥有一个全局的 G 队列
- M 从
队列中提取 G,并执行 - P 的个数就是
GOMAXPROCS(最大256),启动时固定的,一般不修改 - M 的个数和 P 的个数不一定一样多(会有休眠的M 或 P不绑定M )(最大10000)
- P 是用一个全局数组(255)来保存的,并且维护着一个全局的 P 空闲链表
局部G队列与全局G队列的关系
- 全局G任务队列会和各个本地G任务队列按照一定的策略互相交换。没错,就是
协程任务交换 - G任务的执行顺序是,先从本地队列找,本地没有则从
全局队列找 - 转移
- 局部与全局,全局G个数 / P个数
- 局部与局部,一次性转移一半
Gorutine从入队到执行
- 当我们创建一个G对象,就是
gorutine,它会加入到本地队列或者全局队列 - 如果还有空闲的P,则创建一个M 绑定该 P ,注意!这里,P 此前必须还没绑定过M 的,否则不满足空闲的条件。细节点:
- 先找到一个空闲的P,如果没有则直接返回
- P 个数不会占用超过自己设定的cpu个数
- P 在被 M 绑定后,就会初始化自己的 G 队列,此时是一个
空队列 - 注意这里的
一个点!- 无论在哪个 M 中创建了一个 G,只要 P 有空闲的,就会引起新 M 的创建
- 不需考虑当前所在 M 中所绑的 P 的 G 队列是否已满
- 新创建的 M 所绑的 P 的初始化队列会从其他 G 队列中取任务过来
- 这里留下第一个问题:
如果一个G任务执行时间太长,它就会一直占用 M 线程,由于队列的G任务是顺序执行的,其它G任务就会阻塞,如何避免该情况发生? --①
- M 会启动一个
底层线程,循环执行能找到的 G 任务。这里的寻找的 G 从下面几方面找:- 当前 M 所绑的 P 队列中找
- 去别的 P 的队列中找
- 去全局 G 队列中找
- G任务的执行顺序是,先从本地队列找,本地没有则从全局队列找
- 程序启动的时候,首先跑的是主线程,然后这个主线程会绑定第一个 P
- 入口 main 函数,其实是作为一个 goroutine 来执行
解答问题-①
协程的切换时间片是10ms,也就是说 goroutine 最多执行10ms就会被 M 切换到下一个 G。这个过程,又被称为 中断,挂起
原理:
go程序启动时会首先创建一个特殊的内核线程 sysmon,用来监控和管理,其内部是一个循环:
记录所有 P 的 G 任务的
计数 schedtick,schedtick会在每执行一个G任务后递增如果检查到
schedtick一直没有递增,说明这个 P 一直在执行同一个 G 任务,如果超过10ms,就在这个G任务的栈信息里面加一个 tag 标记然后这个 G 任务在执行的时候,如果遇到非内联函数调用,就会检查一次这个标记,然后中断自己,把自己加到队列末尾,执行下一个G
如果没有遇到
非内联函数调用的话,那就会一直执行这个G任务,直到它自己结束;如果是个死循环,并且 GOMAXPROCS=1 的话。那么一直只会只有一个 P 与一个 M,且队列中的其他 G 不会被执行!
例子,下面的这段代码,hello world 不会被输出
func main(){
runtime.GOMAXPROCS(1)
go func(){
fmt.Println("hello world")
// panic("hello world") // 强制观察输出
}()
go func(){
for {
// fmt.Println("aaa") // 非内联函数,这行注释打开,将导致 hello world 的输出
}
}()
select {}
}
中断后的恢复
- 中断的时候将寄存器里的栈信息,保存到自己的 G 对象里面
- 当再次轮到自己执行时,将自己保存的栈信息复制到寄存器里面,这样就接着上次之后运
GOMAXPROCS--性能调优
看完上面的内容,相信你已经知道,GOMAXPROCS 就是 go 中 runtime 包的一个函数。它设置了 P 的最多的个数。这也就直接导致了 M 最多的个数是多少,而 M 的个数就决定了各个 G 队列能同时被多少个 M 线程来进行调取执行!
故,我们一般将 GOMAXPROCS 的个数设置为 CPU 的核数,且需要注意的是:
- go 1.5 版本之前的 GOMAXPROCS 默认是 1
- go 1.5 版本之后的 GOMAXPROCS 默认是 Num of cpu
Golang 的 协程调度机制 与 GOMAXPROCS 性能调优的更多相关文章
- Openresty Lua协程调度机制
写在前面 OpenResty(后面简称:OR)是一个基于Nginx和Lua的高性能Web平台,它内部集成大量的Lua API以及第三方模块,可以利用它快速搭建支持高并发.极具动态性和扩展性的Web应用 ...
- Golang 协程调度
一.线程模型 N:1模型,N个用户空间线程在1个内核空间线程上运行.优势是上下文切换非常快但是无法利用多核系统的优点. 1:1模型,1个内核空间线程运行一个用户空间线程.这种充分利用了多核系统的优势但 ...
- Golang 之协程详解
转自:https://www.cnblogs.com/liang1101/p/7285955.html 一.Golang 线程和协程的区别 备注:需要区分进程.线程(内核级线程).协程(用户级线程)三 ...
- 剑指Offer——知识点储备-故障检测、性能调优与Java类加载机制
剑指Offer--知识点储备-故障检测.性能调优与Java类加载机制 故障检测.性能调优 用什么工具可以查出内存泄露 (1)MerroyAnalyzer:一个功能丰富的java堆转储文件分析工具,可以 ...
- 故障检测、性能调优与Java类加载机制
故障检测.性能调优与Java类加载机制 故障检测.性能调优 用什么工具可以查出内存泄露 (1)MerroyAnalyzer:一个功能丰富的java堆转储文件分析工具,可以帮助你发现内存漏洞和减少内存消 ...
- GoLang之协程
GoLang之协程 目前,WebServer几种主流的并发模型: 多线程,每个线程一次处理一个请求,在当前请求处理完成之前不会接收其它请求:但在高并发环境下,多线程的开销比较大: 基于回调的异步IO, ...
- GO GMP协程调度实现原理 5w字长文史上最全
1 Runtime简介 Go语言是互联网时代的C,因为其语法简洁易学,对高并发拥有语言级别的亲和性.而且不同于虚拟机的方案.Go通过在编译时嵌入平台相关的系统指令可直接编译为对应平台的机器码,同时嵌入 ...
- 图解Go协程调度原理,小白都能理解
阅读本文仅需五分钟,golang协程调度原理,小白也能看懂,超实用. 什么是协程 对于进程.线程,都是有内核进行调度,有CPU时间片的概念,进行抢占式调度.协程,又称微线程,纤程.英文名Corouti ...
- go协程调度
目录 前言 1. 线程池的缺陷 2.Goroutine 调度器 3.调度策略 3.1 队列轮转 3.2 系统调用 3.3 工作量窃取 4.GOMAXPROCS设置对性能的影响 参考 前言 Gorout ...
随机推荐
- js文档节点
一.创建节点: 1.创建元素节点:document.createElement("元素标签名"); 此方法可返回一个 Element 对象 <!DOCTYPE html> ...
- pygame-KidsCanCode系列jumpy-part7-游戏启动/结束画面
通常一个游戏启动(start)或结束(game over)时,都会显示一个画面,来引导用户.这节,我们学习如何处理这块逻辑. 其实之前,我们已经预留了2个函数,只要把它实现即可: def show_s ...
- ssh远程登陆看不到用户名和主机名
使用secure crt远程登陆,发现看不到用户名和主机名,如下图所示 解决方法 sudo vim /etc/passwd root:x:::root:/root:/bin/bash sshd:x:: ...
- java使用httpclient封装post请求和get的请求
在我们程序员生涯中,经常要复用代码,所以我们应该养成时常整理代码的好习惯,以下是我之前封装的httpclient的post和get请求所用的代码: package com.marco.common; ...
- golang的dlv调试工具print打印字符串显示more,无法显示更多
使用dlv (delve golang调试器)打印字符串无法打印全,只能打印一部分(64个字节),在gdb中有 (gdb) set print elements Argument required ( ...
- bimgotoblock-BIM要上区块链
比特币(BitCoin)的暴涨暴跌让参与者体验着过山车的感觉,有人一夜暴富.别墅靠海,也有人拍断大腿或者恨不得跳楼了此残生.世人也更多的听说了比特币底层的依托--区块链(BlockChain) 技术. ...
- UITextField 基本设置
_myAccount = [[UITextField alloc]init]; _myAccount.frame = CGRectMake(, , , ); _myAccount.background ...
- LIBTUX_CAT:466: ERROR: tpopen TPERMERR xa_open returned XAER_INVAL
tmboot 启动Tuxedo服务失败,从ULOG日志中看到以下错误: 100534.MATHXH!TMS_ORA10G.22600.4076.0: LIBTUX_CAT:466: ERROR: tp ...
- html : 九宫格(响应式)
talk is cheap, show you the code : <!DOCTYPE html> <html> <head> <title>html ...
- Java驱动远程连接mongoDB(简明易懂版)
mongodb默认是不能远程连接的,而且在linux安装完你会发现,它的目录极其简单,连个配置文件都没有. 我的mongodb的版本是3.6,目前最新的.https://www.mongodb.com ...