美文网首页程序员
go 协程的实现笔记

go 协程的实现笔记

作者: yellowone | 来源:发表于2020-07-14 17:28 被阅读0次

    看到有一篇写得很清楚的博客,做个笔记。

    原文在此:Golang源码探索(二) 协程的实现原理

    • 有时候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中。

    相关文章

      网友评论

        本文标题:go 协程的实现笔记

        本文链接:https://www.haomeiwen.com/subject/ljpfhktx.html