跳转至

技术笔记

面试存在的问题

问题 解决
答非所问 先确认问题,停顿思考再作答。如果没有理解问题,可以复述后,询问面试官确认问题
回答问题遗漏 有条件的话用纸笔记录问题,没有条件的话先记住123个问题,不要着急想答案,然后根据123分别作答,如果有遗忘,询问面试官问题
回答啰唆 先说结论,然后再拆解为123说原因或具体行动

项目

Go

GMP模型

角色 全称 含义 核心职责
G Goroutine 协程 待执行的任务,用户态轻量级线程,初始栈仅 2KB,按需动态扩容
M Machine 系统线程 真正执行代码的操作系统内核线程,由 OS 内核调度
P Processor 逻辑处理器 包含调度 G 所需的上下文和本地队列,数量通常等于 CPU 核心数(GOMAXPROCS

首先,GMP中的G是用户态的协程,具备完整的栈信息,初始仅2KB,可动态扩容。M是操作系统的线程,由内核调度。P是调度G的逻辑处理器,M 必须绑定一个 P 才能执行代码。 在Go早期,只有GM模型,这导致全局锁竞争激烈,在加入了P之后,就分为了本地队列和全局队列,当创建新的 Goroutine 时,会优先放入本地队列,如果本地队列已满,则取本地队列的一半放入全局队列,避免频繁的全局锁竞争。 如果本地队列为空,则会依次从全局队列、网络轮询器、其他队列窃取任务(全局队列根据M数量窃取,其他队列窃取一半),同时为了避免全局队列的饥饿,本地队列会在每61次调度从全局队列取一个任务。 当G进行阻塞性的系统调用(如阻塞读写磁盘或网络),正在运行它的M会同G一起陷入阻塞状态,此时会把P剥离出来,转交给其他空闲的M。当系统调用结束后,原来的M会尝试重新绑定空闲的P,如果抢不到P,就把当前的G放入全局队列,自己转入休眠。 对于普通的网络I/O(Go封装的非阻塞网络调用),M不会被阻塞,而是会挂载netpoller,M直接执行其他G,数据就绪后再唤醒G。 另外,Go的调度之前是协作式,这导致了某些G可能一直占用M而不会释放,现在已经改为了抢占式,运行超过一定时间会强制中断M的执行。

GC

GC的演变一直在优化STW导致的停顿问题。Go的GC最早是标记-清除算法,整个过程都需要STW,导致程序严重的卡顿;之后引入了并发三色标记和插入写屏障,标记协程可以和用户协程并发执行,显著降低了STW的停顿,由于栈的操作非常频繁,因此在栈上不开启插入写屏障,这导致了标记结束后需要STW再次对栈扫描,确认标记期间的修改;最后引入了混合写屏障,写屏障只作用于堆,栈上全黑且栈上新建对象直接标黑,而堆上对象引用的插入和删除时标记为灰色,避免了栈的STW二次扫描,因此进一步降低STW导致的停顿。

GC的过程是这样的, 1. 标记开始时,开启写屏障,短暂的STW 2. 并发标记,GC协程与用户协程并发执行,GC协程从根对象开始,初始对象都是白色,已完全扫描的对象是黑色,部分扫描的对象是灰色。 3. 标记结束时,关闭写屏障,短暂的STW 4. 并发清理,回收所有标记为白色的对象,该过程没有STW

GC 会在内存达到阈值和超过2分钟没有GC时被触发。在实际的业务中,通过sync.Pool复用对象、预分配容量避免slice/map频繁扩容、减少变量逃逸(小对象优先传值而非指针)等方式来降低GC压力。

协助标记

Go 默认分配 25% 的 CPU 用于后台 GC 标记。如果某个协程分配内存过快(超出后台标记速率),运行时会强制该协程暂停业务代码,转而协助进行三色标记。这样既压制了该协程分配新内存的速度,又借用其 CPU 算力加速了标记的完成。

MySQL

Redis

分布式系统

线上故障

goroutine 泄露

内存泄露

网络问题

AI

使用流程

评论