看到有一篇写得很清楚的博客,做个笔记。
-
有时候G需要调用一些无法避免阻塞的原生代码, 这时M会释放持有的P并进入阻塞状态, 其他M会取得这个P并继续运行队列中的G.
-
因为同一时间只有一个线程(M)可以拥有P, P中的数据都是锁自由(lock free)的, 读写这些数据的效率会非常的高.
-
自旋中(spinning)这个状态非常重要, 是否需要唤醒或者创建新的M取决于当前自旋中的M的数量.
-
本地运行队列有数量限制, 当数量达到256个时会入队到全局运行队列. 本地运行队列的数据结构是环形队列, 由一个256长度的数组和两个序号(head, tail)组成.
-
全局运行队列的数据结构是链表, 由两个指针(head, tail)组成.
-
入队待运行的G后, 如果当前无自旋的M但是有空闲的P, 就唤醒或者新建一个M
-
当M离开自旋状态并准备运行出队的G时, 如果当前无自旋的M但是有空闲的P, 就唤醒或者新建一个M
-
当M离开自旋状态并准备休眠时, 会在离开自旋状态后再次检查所有运行队列, 如果有待运行的G则重新进入自旋状态
-
G从无缓冲的channel获取不到数据, G会保存状态并变为等待中(_Gwaiting)并添加到channel的队列
-
go的调用规范非常的简单, 所有参数都通过栈传递, 返回值也通过栈传递
-
参数和返回值都从低位到高位排列, go函数可以有多个返回值的原因也在于此. 因为返回值都通过栈传递了
-
TLS的全称是Thread-local storage, 代表每个线程的中的本地数据.
-
当函数发现栈空间不足时, 会申请一块新的栈空间并把原来的栈内容复制过去.
-
传递闭包给其他函数时会传递指向"闭包的内容"的指针
-
如果闭包修改了变量, 闭包中的参数会是指针而不是值, 修改时会修改到原来的位置上
-
m0是启动程序后的主线程, 这个m对应的实例会在全局变量m0中, 不需要在heap上分配,
-
m0负责执行初始化操作和启动第一个g, 在之后m0就和其他的m一样了.
-
g0是仅用于负责调度的G, g0不指向任何可执行的函数, 每个m都会有一个自己的g0,
-
在调度或系统调用时会使用g0的栈空间, 全局变量的g0是m0的g0.
-
g0会被设置到TLS中
-
第一个被调度的G会运行runtime.main
-
启动一个新的M执行sysmon函数, 这个函数会监控全局的状态并对运行时间过长的G进行抢占
-
G中保存调度数据是sched变量
-
M中的g0是用于调度的特殊g, 调度和执行系统调用时会切换到这个g
-
M获取g0就是从TLS中获取。
-
如果当前有空闲的P, 但是无自旋的M(nmspinning等于0), 并且主函数已执行则唤醒或新建一个M.
-
为了公平起见, 每61次调度从全局运行队列获取一次G, (一直从本地获取可能导致全局运行队列中的G不被运行)
-
sysmon中有netpool(获取fd事件), retake(抢占), forcegc(按时间强制执行gc), scavenge heap(释放自由列表中多余的项减少内存占用)等处理.
-
通过设置stackguard可以实现抢占,因为会触发栈扩张,栈扩张的时候检查是否等于一个特殊的常量,如果是,协程自身判断是否要抢占。
-
被枪占的g会到全局队列g中。
网友评论