以下为本人阅读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
}

goroutine调度源码阅读笔记的更多相关文章

  1. mxnet源码阅读笔记之include

    写在前面 mxnet代码的规范性比Caffe2要好,看起来核心代码量也小很多,但由于对dmlc其它库的依赖太强,代码的独立性并不好.依赖的第三方库包括: cub dlpack dmlc-core go ...

  2. CI框架源码阅读笔记5 基准测试 BenchMark.php

    上一篇博客(CI框架源码阅读笔记4 引导文件CodeIgniter.php)中,我们已经看到:CI中核心流程的核心功能都是由不同的组件来完成的.这些组件类似于一个一个单独的模块,不同的模块完成不同的功 ...

  3. CI框架源码阅读笔记4 引导文件CodeIgniter.php

    到了这里,终于进入CI框架的核心了.既然是“引导”文件,那么就是对用户的请求.参数等做相应的导向,让用户请求和数据流按照正确的线路各就各位.例如,用户的请求url: http://you.host.c ...

  4. CI框架源码阅读笔记3 全局函数Common.php

    从本篇开始,将深入CI框架的内部,一步步去探索这个框架的实现.结构和设计. Common.php文件定义了一系列的全局函数(一般来说,全局函数具有最高的加载优先权,因此大多数的框架中BootStrap ...

  5. CI框架源码阅读笔记2 一切的入口 index.php

    上一节(CI框架源码阅读笔记1 - 环境准备.基本术语和框架流程)中,我们提到了CI框架的基本流程,这里再次贴出流程图,以备参考: 作为CI框架的入口文件,源码阅读,自然由此开始.在源码阅读的过程中, ...

  6. 源码阅读笔记 - 1 MSVC2015中的std::sort

    大约寒假开始的时候我就已经把std::sort的源码阅读完毕并理解其中的做法了,到了寒假结尾,姑且把它写出来 这是我的第一篇源码阅读笔记,以后会发更多的,包括算法和库实现,源码会按照我自己的代码风格格 ...

  7. Three.js源码阅读笔记-5

    Core::Ray 该类用来表示空间中的“射线”,主要用来进行碰撞检测. THREE.Ray = function ( origin, direction ) { this.origin = ( or ...

  8. PHP源码阅读笔记一(explode和implode函数分析)

    PHP源码阅读笔记一一.explode和implode函数array explode ( string separator, string string [, int limit] )此函数返回由字符 ...

  9. AQS源码阅读笔记(一)

    AQS源码阅读笔记 先看下这个类张非常重要的一个静态内部类Node.如下: static final class Node { //表示当前节点以共享模式等待锁 static final Node S ...

随机推荐

  1. 洛谷 P4017 【最大食物链计数】

    看到这种明显的有向无环图,并且等级分明,自然而然就能想到拓补排序啦.对于这道题,我们就可以利用最短路计数的那种思想(不知道也没关系),设\(j\)是\(i\)的后继,\(dis_i\)表示以\(i\) ...

  2. iview表单验证--数字必填+校验

    直接使用: { required: true, type:"integer", message:"请填写整数", trigger: "blur&quo ...

  3. vs2017+opencv3.4.0的配置方法

    1.尝试了这个博客的方法: https://blog.csdn.net/u014574279/article/details/50909425/ 结果: 无法打开文件“opencv_ml2410d.l ...

  4. [Python] datetime.timedelta模块用法

    python使用datetime模块timedelta实现日期时间相加: python计算明天的日期: from datetime import datetime from datetime impo ...

  5. H5调用手机的相机/摄像/录音等功能 _input:file的capture属性说明

    H5使用input标签调用系统默许相机,摄像,录音功能.使用input:file标签, 去调用系统默认相机,摄像,录音功能,其实是有个capture属性,直接说明需要调用什么功能: <input ...

  6. 转载---最简单的JavaScript模板引擎

    转载自:http://www.cnblogs.com/dolphinX/p/3489269.html,http://blog.jobbole.com/56689/

  7. 关于ganymed-ssh2版本262和build210的SCPClient类的区别

    ganymed-ssh2是通过java使用ssh连接服务器的工具库,先上两个版本的pom文件配置: <!--ssh连接linux--> <!-- https://mvnreposit ...

  8. Linux系统 Centos7 环境基于Docker部署Rocketmq服务

    消息队列 基本概述 MQ,Message Queue,基于TCP协议构建的简单协议,区别于具体的通信协议. 基于通信协议定义和抽象的更高层次的通信模型,一般都是生产者和消费者模型,又或者说服务端和客户 ...

  9. postman-5-授权

    授权 Inherit auth from parent 假设现在将一个文件夹添加到集合中.在授权选项卡下,默认授权类型就被设置为“从父继承授权”“从父继承授权”设置表示默认情况下此文件夹中的每个请求都 ...

  10. Window - 安装 Jenkins

    安装方式 war 包放 Tomcat 下 直接安装 jenkins.msi 环境准备 安装 jdk.tomcat,参考:https://www.cnblogs.com/poloyy/p/1326781 ...