GMP源码剖析
1. GMP简介
G: goroutine,用户级线程或协程,在用户态调度
M:machine,内核级线程,在内核态调度
P:processor,存放G队列,P必须绑定M,队列中的G才可以执行
m0:主线程对应的结构
g0:每个M都绑定一个g0,栈的大小为8K,用来执行runtime的代码
2. 入口函数
_rt0_【ARCH】_【OS】,例如ARCH为amd64,OS为linux的入口函数为_rt0_amd64_linux,如下图:
3. TLS
TLS(Thread Local Storage),每个线程会绑定m.tls的地址,tls类型为[6]uintptr,首个元素为当前运行的g,g中保存m
4. 运行前的准备工作
a. 初始化m0所绑定的g0的栈,大小约64k,区别于其他m所绑定的栈为8K的g0,如下图:
b. 绑定m0与g0,如下图:
c. 命令行参数、系统信息、调度等初始化,如下图:
5. 主goroutine的创建
main函数所在的goroutine的创建,如下图:
其它goroutine由go关键字来创建,编译器会自动把go关键字转成newproc函数
6. goroutine调度
每个的线程的入口函数都是mstart,mstart执行schedule,schedule依次从本地P、全局P、其它P、netpoll中取g
如果都没有取到,线程则休眠,一旦取到,则调用execute来执行,若当前goroutine执行完毕,则调用goexit0继续调度
自动执行goexit0的原因在于创建goroutine的时候,把goexit的函数地址设定到goroutine根函数返回时pop所在的栈位置
ret指令会执行pop pc,如下图:
7. 系统监控
goroutine会专门启动一个线程来处理netpoll、长时间运行的goroutine,如下图:
a. 如果已经有可读或可写的goroutine并且超过10ms得不到执行,则加入全局P中
b. 如果某个goroutine长时间处于运行状态超过10ms,则会被标记成抢占状态,待调用某个函数时因栈溢出触发morestack函数,进而被其他goroutine抢占
c. 如果某个goroutine处于系统调用状态,如果此时本地P还有别的goroutine,或者执行时间超过10ms,则唤醒一个M与这个P绑定,进而P中剩下的goroutine可以得到执行