Go学习笔记
- 逃逸分析
堆和栈:函数里面的临时变量一般放到栈上,全局变量或者new创建的变量会放到堆上。堆和栈相比,堆适合不可预知大小的内存分配,但是分配速度较慢,可能带来内存碎片。而栈的内存分配速度很快,并且会自动释放。
逃逸分析:当一个对象的指针被多个方法或线程引用时,则称这个指针发生了逃逸。逃逸分析决定了这个变量是放到堆上还是栈上。
编译器会根据变量是否被外部引用来决定是否逃逸:如果变量在函数外部没有引用,则优先放到栈上;
,如果变量在函数外部存在引用,则必定放到堆上。查看变量是否发生逃逸:go build -gcflags '-m -l' main.go
或者反编译go tool compile -S main.go
- 垃圾回收
Go 的 GC 方式为追踪式:从根对象出发,根据对象之间的引用信息,一步步推进直到扫描完毕整个堆并确定需要保留的对象,从而回收所有可回收的对象。具体来说是无分代(对象没有代际之分)、不整理(回收过程中不对对象进行移动与整理)、并发(与用户代码并发执行)的三色标记清扫算法: 白色对象(可能死亡):未被回收器访问到的对象。在回收开始阶段,所有对象均为白色,当回收结束后,白色对象均不可达。 ,灰色对象(波面):已被回收器访问到的对象,但回收器需要对其中的一个或多个指针进行扫描,因为他们可能还指向白色对象。 ,黑色对象(确定存活):已被回收器访问到的对象,其中所有字段都已被扫描,黑色对象中任何一个指针都不可能直接指向白色对象。
-
make和new的区别
-
引用类型有:切片,映射,通道,接口和函数。
-
值接收者使用值的副本来调用方法。指针接收者使用值本身来调用方法。通过接口类型的值调用方法,如果接收者定义为指针类型,接口类型的值必须是指针。
-
闭包=函数+引用环境。匿名函数就是闭包。可以把闭包看成是一个类,一个闭包函数调用就是实例化一个类。
-
context用来解决gotoutine之间退出通知、元数据(例如token,超时时间)传递的问题。
-
goroutine和线程的区别
- 内存消耗。一个goroutine的栈内存占用2KB,而一个线程则需要消耗1MB栈内存;
- 创建和销毁。线程是内核态的,创建和销毁对操作系统来说消耗比较大,而goroutine是用户态的,由go runtime管理,消耗非常小;
- 切换。线程切换需要保存很多寄存器状态,而goroutine切换只需要保存极少的寄存器,因此切换效率比线程高很多。
- GMP
- G:goroutine,G 会在 M 上得到执行;
- M:machine 系统线程。G 需要调度到 M 上才能运行,M 是真正工作的人。M 保存了自身使用的栈信息、当前正在 M 上执行的 G 信息、与之绑定的 P 信息。当 M 没有工作可做的时候,在它休眠前,会“自旋”地来找工作:检查全局队列,查看 network poller,试图执行 gc 任务,或者从其他 P “偷”工作;
- P:processor 调度器,保存了本地可运行的 G 队列。一个 M 只有绑定 P 才能执行 goroutine,当 M 被阻塞时,整个 P 会被传递给其他 M 接管。
GPM 三足鼎力,共同成就 Go scheduler。G 需要在 M 上才能运行,M 依赖 P 提供的资源,P 则持有待运行的 G。你中有我,我中有你。
GMP 的核心思想是:
- 线程复用(work stealing, hand off);
- 限制同时运行(不包含阻塞)的线程数为 N,N 等于 CPU 的核心数目;
- 线程私有的 runqueues,并且可以从其他线程 stealing goroutine 来运行,线程阻塞后,可以将 runqueues 传递给其他线程。
Go 程序启动后,会给每个逻辑核心分配一个 P(Processor调度器);同时,会给每个 P 分配一个 M(Machine,表示内核线程),这些内核线程仍然由 OS scheduler 来调度。在初始化时,Go 程序会有一个 G(initial Goroutine),执行指令的单位。G 会在 M 上得到执行,内核线程是在 CPU 核心上调度,而 G 则是在 M 上进行调度。
为什么需要 P 这个组件,直接把 runqueues 放到 M 不行吗?
- P维护一个本地队列,避免全局队列带来的锁竞争;
- 当线程阻塞的时候,P可以把其他G分配给别的线程。
goroutine 调度的时机: 1.go 关键字创建;2.GC;3.系统调用;4.atomic,mutex,channel 等操作会使 goroutine 阻塞,因此会被调度走。