go 协程的实现笔记

yellowone · · 508 次点击 · · 开始浏览    
这是一个创建于 的文章,其中的信息可能已经有所发展或是发生改变。

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

原文在此: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中。


有疑问加站长微信联系(非本文作者)

本文来自:简书

感谢作者:yellowone

查看原文:go 协程的实现笔记

入群交流(和以上内容无关):加入Go大咖交流群,或添加微信:liuxiaoyan-s 备注:入群;或加QQ群:692541889

508 次点击  
加入收藏 微博
暂无回复
添加一条新回复 (您需要 登录 后才能回复 没有账号 ?)
  • 请尽量让自己的回复能够对别人有帮助
  • 支持 Markdown 格式, **粗体**、~~删除线~~、`单行代码`
  • 支持 @ 本站用户;支持表情(输入 : 提示),见 Emoji cheat sheet
  • 图片支持拖拽、截图粘贴等方式上传