简单描述下三色标记法。

三色标记法基于标记清除的 gc 方法,初始化所有的节点为白色,然后从 root 开始扫描,引用到的节点为灰色,然后再从灰色节点扫描,并将灰色变为黑色节点,重复扫描灰色节点直到灰色节点消失,就只剩下白色和黑色节点,清除掉白色节点。

golang 是如何优化 stw 的?

golang没有根本解决stw,只是通过并行执行标记和清除过程缩短stw的时间。

stw 在 gc 过程中怎么触发的?

gc中有两个阶段触发了 stw。第一次是在 gc 初始化的时候,启用 gc 辅助功能和写屏障。第二次是在re-scan 中,即第一次扫描完成,在进行第二次扫描新引用的节点时。

什么是写屏障?

写屏障是用来在 gc 扫描的过程中,如果有新的节点被引用,那么会在引用的过程中记录下来,通知 gc,然后 gc 就不会漏掉新的被引用节点了。

局部变量分配在栈还是堆上?

默认是分配在栈上的,但是如果变量存在逃逸分析,那么就必须分配到堆上。

defer 的执行顺序。

如果有多个defer,执行顺序是后进先出。也就是说 defer A;defer B;那么会先执行B,再执行A。

defer 和 return 的执行顺序。

首先,return 不是一个原子操作,分为revl = xx;retu revl,defer的执行顺序在这两个原子操作之间,也就是revl=xx;defer xx;retu revl。

string 和 nil。

string 是不能赋值为 nil 的,不过有个小技巧,可以使用 *string 设置为 nil 。

逃逸分析

逃逸分析是指判断一个变量有没有脱离原来的作用域被引用,如果没有,变量的存储就是在栈上,否则就必须存储在堆上。众所周知,go 1.13 提升了 defer 30% 的性能,这个提升其实就是根据逃逸分析优化的,更少地在堆上存储提升性能。

make 和 new 的区别

普通的变量声明,都会默认赋予零值,并且可以直接修改,例如 var i int。但是对于引用类型 var i *int,在进行修改的时候就会直接** panic 无效的内存地址**,可见声明之后并没有给指向的变量分配内存。因此就需要newmake出场了,两者都是用来初始化内存的,但是 new 初始化并赋予零值,返回变量的地址。make 则只适用于 channel、slice、map,因为这三者本身就是引用类型,初始化为非零值且返回对象的值而不是地址。

程序初始化顺序

从 main 函数开始,首先初始化 import, 循环深入到导入包的内部,依次初始化 import、全局变量、init 函数,然后依次返回,最后返回到 main 函数。

goroutine 是不是抢占式

是,有专门一个 M,叫做 sysmon,用来监控线程。当一个 goroutine 运行超过 10ms,sysmon 就会发起抢占式请求。

GPM 模型简述

G goroutine,P Process 线程,M machine 系统线程。

M 从 P 的运行队列中取出 G 运行,当 P 的运行队列为空,则会从 Global 队列中取,若 Global 队列为空,则会去其他的 P 的运行队列中取。

当 G 遇到 channel 或者 I/O 操作阻塞后,则会被放到对应 P 的等待队列中。

P 的数量是由 GOMAXPROCS 指定。

CSP 和 Actor 模型

两者都是并发的处理模型,Actor 模型中 actor 是主体,actor 之间传递消息不需要中介,并且是异步的。golang 实现的 CSP 则是通过 channel 来进行消息传递,并且是同步的。