Runtime
go的runtime包是经典八股,共分为内存管理,GMP和垃圾回收
GMP
GMP是go的用户态的调度模型,不会涉及到内核态的线程的切换,因此它的切换非常快;并且一个协程都是KB的内存,相比于线程的MB更加的轻量化,它通过本地编译的runtime包实现的。
-
G
协程,通过go function启动,分为G0和G1,其中G0是生成P的时候也一起生成的,它比较特殊,用于垃圾回收和协程的调度;与线程不同的是,g的设定是抢占式的,它有自己的时间片,当时间片用完后,会主动让出资源,因此没有饥饿问题
G中有一个比较特殊的,比如是main函数生成的协程,如果它退出的时候,它的子协程是会退出的;但是它的子协程的子协程,是不会退出的,main的孙子协程对main协程是没有感知的,同时main协程对于它的孙子协程也没用感知;如果需要感知的话,使用chan进行通信 -
M
操作系统的内核线程,一般一个M和一个P绑定,如果M陷入了休眠,那么P会从这个M上剥离,去一个空闲的M上或则新建一个M,因此程序的M>=P -
P
p是g运行的时候所必须的资源,g要运行,必须在P上运行;P的个数的设置限定了最大并发数量,通过 GOMAXPROCS来设定;P有两个队列,一个是本地队列,一个是全局队列,本地队列是这个P单独享用的,全局队列是所有的P来享用的;
当G创建一个子协程的时候,它会优先放到本地队列里
如果本地队列满了,会根据算法,取一半的g放到全局队列中;
如果P的本地队列为空,那么会采取work-stealing机制去别的P的本地队列或则全局队列里偷取一个G而不是销毁这个P
如果P所绑定的M进入睡眠,那么这个P会主动的handoff,去寻找绑定空闲的M或则自己新建一个M
内存管理
垃圾回收
垃圾回收的重点是两点:减少STW和正确的回收
go中回收的区域有两种:栈和堆,因为栈上频繁的调用,因此栈上不启动屏障
-
强三色一致性
黑色对象不能引用白色对象 -
弱三色一致性
黑色对象能引用白色对象,但是前提是这个白色对象被灰色对象引用 -
写屏障
黑色对象引用新对象,这个对象必须设定为灰色的 -
删除屏障
删除一个对象的引用,这个对象会被设定为灰色
早期的垃圾回收是基于标记清理法,会有大量的stw;
三色标记法回收,因为栈上频繁的调用弹出,因此不对栈上使用写屏障,如果栈上不使用stw的话,那么栈上的对象会被错误的回收;但是用了stw的话,则使用体验不是很好; -
混合写屏障
优先对栈上扫描,并且栈上所有对象都是黑色的
GC期间所有栈上的新建对象都是黑色的
栈上没有启动屏障,全都是黑色的
被删除的对象都是灰色的(删除屏障)
被添加的对象都是灰色的(写屏障)
堆上启动写屏障和删除屏障
逃逸
逃逸就是编译器根据逃逸分析的结果决定将变量初始化在栈上还是初始化在堆上
函数内联就是将函数优化最后在栈上
在go里,无论是动态new出来还是函数的变量,都是经过逃逸分析后最后决定在栈or堆
如果这个变量没有被外部引用,则优先分配在栈上
如果这个变量被外部引用,那么一定分配在堆上
因此,比如说chan,slice这种引用类型,那么一定是分配到堆上的
Make和New
make是针对chan,slice和map
因为make在编译期,会通过make生成一系列的指令,并不是一个简单的分配内存
而new则是分配内存,如果一个普通的结构体或则int没有new,那么则无内存分配,会报错