以下为本人阅读goroutine调度源码随手记的笔记,现在还是一个个知识点的形式,暂时还没整理,先发到这里,一点点更新:
1). runq [256]guintptr P 的runable队列最大只能保存256个G
2). 全局的可执行队列由schedt持有,runq gQueue
3). goroutine 状态从_Grunning转为_Grunnable后,解除g与m的关联,将g放入全局的任务队列,然后再找到一个新的_Grunable状态的G来执行
- func goschedImpl(gp *g) {
- status := readgstatus(gp)
- if status&^_Gscan != _Grunning {
- dumpgstatus(gp)
- throw("bad g status")
- }
- casgstatus(gp, _Grunning, _Grunnable)
- dropg()
- lock(&sched.lock)
- globrunqput(gp)
- unlock(&sched.lock)
-
- schedule()
- }
4). 寻找下一个g来执行的函数 findrunnable 在proc.go:2178
大致分为三大步骤,一是尝试从本地队列获取一个,二是尝试从全局队列获取n个,三是尝试窃取。这里要说一下的是:从本地队列获取时和从全局队列获取时关于并发访问的控制是不一样的,从本地队列获取的时候,都是通过原子操作来和自旋锁来实现的,而从全局队列获取时,则是通过获取sched.lock锁控制的,这是一个全局的排他锁。
首先尝试从本地队列获取(本地队列先看runnext是否不为0,如果是,先返回runnext指向的g,否则从队首弹一个g出来),如果本地队列没有则从全局队列获取,如果全局队列也没有,就尝试从其他P的队列中窃取,但是窃取之前会先做如下几个判断:
1. sched.npidle == gomaxprocs - 1 也就是看其他的P是不是都空闲着,如果是的话,就没必要去窃取了。
2. 2*sched.nmspinning >= gomaxprocs - npidle 当前处于spinning状态的M的两倍 大于等于非空闲的P的数量. 翻译过来就是在正在窃取G的M的数量应该不超过非空闲状态的P的一半
3. 是否准备执行GC,如果是,就先不窃取了
5).从全局队列取一部分g到本地队列的操作。
源码位置: proc.go:4665 globrunqget (go1.12)
关于取多少个的问题。假设全局队列一共有t个G,一共有n个P,则,取的数量不超过t / n,并且不超过本队队列总长度的一半,目前1.14版本中,本地队列总长度为256,也就是每次从全局队列取的G的数量不超过128个.
6).关于选取哪个P进行窃取以及窃取多少个的问题:
源码位置:proc.go:2238 findrunnable(go1.12)
1. 选取窃取的P
首先一共进行4轮尝试,每轮都会遍历所有的P。
另外这里的遍历P的过程值得说一下,并不是从下标0位置一个个往后遍历的,而且跳跃式的遍历,而且跳跃的步长是随机出来的。关于步长的选取,算法是这样的:
首先定义了一个randomOrder结构如下:
type randomOrder struct {
count uint32 // P的数量
coprimes []uint32 // 小于等于count,且与count互质的数(从小到大排列)
}
首先会生成一个与P的数量互质的数的列表,然后随机出一个数i,遍历P列表的步长= i % len(coprimes)。开始遍历的位置=i % nprocs (nproc即P的数量,源码中是用i对count取模,而count的值是通过randomOrder.reset操作把nprocs赋值给count的).
有意思的是,当第一轮遍历完了之后,发现一个g都没拿到,这个时候就会把stealRunNextG的值置为true,这个值表示是否从别的P的runnext窃取,第一轮窃取的时候这个值都为false。(也就是饥不择食了)
7). 窃取数量
源码位置: runqgrab proc.go:4858
假设现在P1从P2窃取一部分G。关于窃取多少个G是怎么定的呢?有以下几点原则,第一,假设P2的可执行队列中G的数量为x个,窃取个数n = x - x / 2;第二不得超过P1的可执行队列(环形buffer)容量的一半,当前最新版本(go1.14)中P的可执行队列最大容量为256,因此,窃取G的个数不超过128个。
8). 最终没有找到可执行的G之后发生了什么
1. 断开m和p之间的联系
2. 把P放进sched.pidle(空闲P的链表)
3. 把M的spinning(是否正在获取可用G)状态改为false
4. 再次确认一遍所有的P的runq都是空的
5. 尝试轮训阻塞事件列表,看看是否有一些G已经转变为runable状态.此处获取到的gList将尝试空闲的P列表中取一个P出来,然后把gList放进P的runq。而如果没有空闲的P,那么将把gList放进全局的runq。
6. 若步骤5仍然没能获取到可执行的G,或者步骤5获取到了G,但是没有空闲的P,那么将这部分G放到全局队列后,M将停止运行,将当前M加入到空闲M列表,然后开始睡眠,直到下一次被唤醒。
9).全局队列的来源有以下几种:
1. 轮询等待队列,解除等待之后将放入全局队列
2. 在M上正常执行的g,被切换之后被放入全局队列
3. 插入新的goroutine时,如果本地队列已满,则会将本地队列的一半取出来,再加上新加入的g,一起放入全局队列,放入全局队列前,会将取出来的这个列表随机打乱顺序再放入proc.go:4803(go1.12)
10).一个go程序在64位机上,最小的栈空间大小是2048个字节,最大的栈空间大小是1G,在32位机上是250M,不过这里要注意,这里的1GB并不等于1024M,而是10的9次方字节,proc.go中有这样一句话:Using decimal instead of binary GB and MB because they look nicer in the stack overflow failure message.因为看起来更好看。
11).go关键字新建一个协程时,传的参数大小是有限制的,最大是2048个字节,实际上比这个还要小一点(经测试,参数最大为2000是没问题的,2001个字节的时候,会抛出这个错误:newproc: function arguments too large for new goroutine.
12).新建G时,不会总是重新分配,而是先尝试从本地空闲的G 列表里面取一个,如果本地空闲G列表没有,则从全局空闲G列表中取,如果都没取到,就重新分配一个。
13).关于新创建的G会放到哪个P的队列的问题
以下是runqput函数的注释,在创建新的G之后,传入的next始终是true的,所以,新创建的G会放到runnext,因此,新创建的G会在当前的G被换出后,立即执行,不用排队。
// runqput tries to put g on the local runnable queue.
// If next is false, runqput adds g to the tail of the runnable queue.
// If next is true, runqput puts g in the _p_.runnext slot.
// If the run queue is full, runnext puts g on the global queue.
14). sudog用来保存g的等待队列,例如channel的发送或接收等待队列
type sudog struct {
g *g
isSelect bool
next *sudog
prev *sudog
elem unsafe.Pointer // data element (may point to stack)
acquiretime int64
releasetime int64
ticket uint32
parent *sudog // semaRoot binary tree
waitlink *sudog // g.waiting list or semaRoot
waittail *sudog // semaRoot
c *hchan // channel
}
- mxnet源码阅读笔记之include
写在前面 mxnet代码的规范性比Caffe2要好,看起来核心代码量也小很多,但由于对dmlc其它库的依赖太强,代码的独立性并不好.依赖的第三方库包括: cub dlpack dmlc-core go ...
- CI框架源码阅读笔记5 基准测试 BenchMark.php
上一篇博客(CI框架源码阅读笔记4 引导文件CodeIgniter.php)中,我们已经看到:CI中核心流程的核心功能都是由不同的组件来完成的.这些组件类似于一个一个单独的模块,不同的模块完成不同的功 ...
- CI框架源码阅读笔记4 引导文件CodeIgniter.php
到了这里,终于进入CI框架的核心了.既然是“引导”文件,那么就是对用户的请求.参数等做相应的导向,让用户请求和数据流按照正确的线路各就各位.例如,用户的请求url: http://you.host.c ...
- CI框架源码阅读笔记3 全局函数Common.php
从本篇开始,将深入CI框架的内部,一步步去探索这个框架的实现.结构和设计. Common.php文件定义了一系列的全局函数(一般来说,全局函数具有最高的加载优先权,因此大多数的框架中BootStrap ...
- CI框架源码阅读笔记2 一切的入口 index.php
上一节(CI框架源码阅读笔记1 - 环境准备.基本术语和框架流程)中,我们提到了CI框架的基本流程,这里再次贴出流程图,以备参考: 作为CI框架的入口文件,源码阅读,自然由此开始.在源码阅读的过程中, ...
- 源码阅读笔记 - 1 MSVC2015中的std::sort
大约寒假开始的时候我就已经把std::sort的源码阅读完毕并理解其中的做法了,到了寒假结尾,姑且把它写出来 这是我的第一篇源码阅读笔记,以后会发更多的,包括算法和库实现,源码会按照我自己的代码风格格 ...
- Three.js源码阅读笔记-5
Core::Ray 该类用来表示空间中的“射线”,主要用来进行碰撞检测. THREE.Ray = function ( origin, direction ) { this.origin = ( or ...
- PHP源码阅读笔记一(explode和implode函数分析)
PHP源码阅读笔记一一.explode和implode函数array explode ( string separator, string string [, int limit] )此函数返回由字符 ...
- AQS源码阅读笔记(一)
AQS源码阅读笔记 先看下这个类张非常重要的一个静态内部类Node.如下: static final class Node { //表示当前节点以共享模式等待锁 static final Node S ...
随机推荐
- 线程的创建方式以及synchronize的使用
线程的两种创建方式 继承 Thread class Thr extends Thread { @Override public void run() { Thread.cur ...
- Mac上使用Docker Desktop启动Kubernetes,踩坑后终于搞掂
1 前言 Kubernetes又简称k8s,是Google开源的容器集群管理系统,最近也是火热.闲来无事(为了发文),捣鼓了一下,在Mac上搭建Kubernetes,遇到一些坑,也记录一下. 另外,D ...
- 如何将less编译成css文件__less自动编译成css的方法总结
作为css的预处理less,拥有着比css更快捷方便,扩展了css的变量.Mixin.函数等特性,使 CSS 更易维护和扩展. 如何你已经回使用css,那么less就很容易上手了.如果不使用less ...
- Synchronized锁的是什么?
Synchronized锁的是什么? 临界区与锁 并发编程中不可避免的会出现多个线程共享同一个资源的情况,为了防止出现数据不一致情况的发生,人们引入了临界区的概念.临界区是一个用来访问共享资源的代码块 ...
- MySQL 事务 异常 事务隔离的级别
MySQL 事务 异常 事务隔离的级别 事务 在你操作数据库的同时,有可能其他用户还会不断地对数据进行增删改查操作.为了避免并行进行时出现混乱,就产生了"事务".事务就是要保证 ...
- 【线型DP】CF1012C Hills 小山坡
来了来了. 题目: 给你n个数,你一次操作可以把某一个数-1(可以减为负数),你的目标是使任意的k个数严格小于它旁边的两个数(第一个数只用严格小于第二个数,第n个数只用严格小于第n-1个数),问最少需 ...
- Python 最强 IDE 详细使用指南!
PyCharm 是一种 Python IDE,可以帮助程序员节约时间,提高生产效率.那么具体如何使用呢?本文从 PyCharm 安装到插件.外部工具.专业版功能等进行了一一介绍,希望能够帮助到大家.作 ...
- Windows系统appium移动端自动化真机环境搭建
appium-windows-android环境搭建完成以后,就可以进行真机模式下的appium环境搭建啦!! 准备:把要测试的app下载至本机(小波的是把apk放在桌面上,例如:C:\Users\w ...
- HDFS读写流程(重点)
@ 目录 一.写数据流程 举例: 二.异常写流程 读数据流程 一.写数据流程 ①服务端启动HDFS中的NN和DN进程 ②客户端创建一个分布式文件系统客户端,由客户端向NN发送请求,请求上传文件 ③NN ...
- 解决android studio 文本乱码问题
下面图片,部分字体,有一些中文字符无法显示,可选择提交保存,立即可看到效果,不喜欢就再换一个合适的字体.