以下列出了一些常见的 GoLang 面试问题以及基于作者经验的相应答案。该列表会经常更新新的理解。敬请关注。
GoLang 的 GMP 模型是什么?
GoLang 使用 goroutine 来实现并发,并且以高并发支持而闻名,因为该语言定义了自己的 goroutine 调度和处理系统,该系统被称为 GMP 模型。
它的工作原理是,M 通常被定义为正在产生的 OS 线程(然后每个 M 线程都应该与一个 Process 队列相关联,并且每个 Process 队列可以有多个 goroutine。Process 队列的数量由 GOMAXPROCS()
定义。当 M 处理完与其关联的 P 中的所有 G 时,它会从其他 M 窃取 G,也会从全局 P 队列中窃取。
如果 M 由于 IO 阻塞或网络阻塞而挂起,它会从其 P 中分离出来,并且此 P 将与另一个可用的 M 相关联,如果不存在,则会创建一个新的 M。
如果 goroutine 无法放入本地 P 中,它将被放入全局 P 队列中。如果 M 无法从其他 M 窃取 G,它将从全局 P 队列中获取。
每个 goroutine 只有 2K 的堆栈大小,这非常小,这就是 GoLang 支持更多并发的原因。并且这种 goroutine 执行之间的切换是基于用户线程空间的,因此切换上下文的成本与线程或进程相比并不高
GoLang 中的 GC 机制是什么?
有三个阶段:Mark Setup(标记设置)、Mark(标记)和 Mark Termination(标记终止)。Mark Setup 和 Mark Termination 是 STW(Stop The World)。Mark 阶段是并发的。
在进行 Mark Setup 时,将启用 Write Barrier(写屏障)以确保数据完整性,然后它将等待所有正在运行的 goroutine 停止。
当 Marking 开始时,它将占用 25% 的 CPU,并为自己分配一个 P 来进行垃圾回收。标记阶段将标记仍在使用的头部,它会尝试查看所有 goroutine 的堆栈并遍历其根指针,然后从根遍历并进行标记。如果标记无法及时完成,它将进行 Mark Assist(标记辅助),这意味着要求其他应用程序 goroutine 帮忙。以加速标记。
在进行 Mark Termination 时,Write Barrier 将被关闭,并且将完成清理工作。
何时开始进行 GC?
- GC Percentage(GC 百分比),即与先前 GC 周期中使用的内存相比,要分配的新内存的百分比。
- 通过调用 runtime.GC() 手动触发
Slice 的底层工作原理是什么?
Slice 描述的是数组的一部分,它与数组不同。
Slice 头部具有长度、容量和零元素指针。底层由数组支持。如果达到容量,则无法再增长,需要重新创建底层数组并重新分配。如果未达到容量,则可以在需要时增长。
Panic、defer 和 recover
对于 defer,它是如何以 FILO(先进后出)方式工作的
-
延迟函数的参数在评估 defer 语句时进行评估。
-
延迟函数调用在周围函数返回后以先进后出的顺序执行
-
延迟函数可以读取和赋值给返回函数的命名返回值。
Panic 将返回执行,并且函数中的 defers 将继续执行。
Recover 仅在延迟函数内部有用。在普通函数中,recover 没有效果,什么也不做。
Defer recover 仅适用于当前 goroutine,它不适用于其衍生的 goroutine。
Context,它非常强大
Context 接口中的 4 个函数:
- Deadline(),
- Done(),
- Err(),
- Value(key interface{})
这样做的好处是资源共享、goroutine 控制
有一些派生的 goroutine,例如 WithCancel、WithDeadline、WithTimeout、WithValue
WithCancel:
-
Cancel 函数将级联到子上下文
-
创建新的子上下文时,它将在父上下文中注册。
WithValue
-
该键必须是可比较的,因为在调用 Value() 函数时会进行 c.key == key 检查
-
slice、map 和 function 不可比较。主要是因为 slice 不可比较,因为它是一种引用类型,仅比较地址没有意义,并且由于 len/cap 特性,比较 slice 中的内容会增加复杂性
Channel
Channel 是一种通过通信共享内存的方式
有缓冲和非缓冲 channel
- 关闭 nil 或已关闭的 channel 会 panic
- 向 nil channel 发送值会永远阻塞,关闭的 channel 会 panic
- 从 nil channel 接收数据会永远阻塞,关闭的 channel 会立即返回
何时会发生 goroutine 泄漏?
一些场景包括:
- 当 channel 未关闭,而 goroutine 一直在等待从 channel 接收数据时
- 当某些资源未正确关闭,而某些 goroutine 仍在等待来自另一端的响应,并且没有为阻塞 IO 设置足够的超时时间时
new() 和 make() 之间有什么区别?
new()
只会分配内存,而 make()
会初始化数据
使用 new()
创建的对象已准备好使用,但需要在需要时显式初始化其属性才能使用
make()
只能用于 slice、map 和 channel 类型。
详细的区别可以在这里找到。
指针和非指针方法接收器之间的区别?
就像函数调用中的按引用传递与按值传递一样。
如果要改变 struct,或者 struct 对于接收器来说太大,可以选择定义指针方法接收器。
如果某些函数具有指针接收器,则其余函数也应该具有指针接收器,以保持一致性。
如何检查 struct 是否实现了接口?
GoLang 中没有像 Java 等其他语言那样的 implements 关键字。如果 struct 实现了接口的所有方法,则认为它实现了该接口。如果 struct 代码与接口定义不在同一位置,则可能难以找出 struct 是否实现了特定接口。在某些情况下,这可能会导致运行时问题。
有几种方法可以检查:
- 使用虚拟对象进行类型断言。
var _ = (SomeStruct{}).(SomeInterface)
- 使用反射。 reflect.TypeOf(OneInterface).Implements(reflect.TypeOf(SomeInterface).Elem())
有关其他方法,请访问这里。