Go 面试汇总

作为一个跨界的 Java Boy 面对 Go 的岗位还是先整理一波。
Go 的 Goroutine
CSP,全称Communicating Sequential Processes,意为通讯顺序进程,它是七大并发模型中的一种,它的核心观念是将两个并发执行的实体通过通道channel连接起来,所有的消息都通过channel传输,Go语言对CSP并发模型的实现——GPM调度模型。
GPM代表了三个角色,分别是Goroutine、Processor、Machine。

Goroutine:承担运行的实体Processor: 联通 G 和 M 的处理器Machine: 操作系统的调度单位:线程

Machine 和 Processor 是 1:1,我们创建的 Goroutine 会通过放入 Queue 让 Processor 在 Machine 执行。
不过值得注意,在实现的过程中,有一种名为 work-stealing 的算法,如果某个 Machine 的 Queue 为空的时候会尝试从其他地方偷点回来。
单纯的从图上看,我们至少提出几个问题
Goroutine如何调用了Systemcall在阻塞状态会怎么样Goroutine如何保证时间公平
Go的调度
OS 调度器是一个抢占式调度器。Go 调度器在内核之上的用户空间中运行。Go 调度器的当前实现不是抢占式调度器,而是协作式调度器。
Go 的状态也有三种状态:
Waiting:这意味着Goroutine 已停止并等待一些事情以继续。这可能是因为等待操作系统(系统调用)或同步调用(原子和互斥操作)等原因。这些类型的延迟是性能下降的根本原因。Runnable:这意味着 Goroutine 需要M上的时间片,来执行它的指令。如果同一时间有很多 Goroutines 在竞争时间片,它们都必须等待更长时间才能得到时间片,而且每个 Goroutine 获得的时间片都缩短了。这种类型的调度延迟也可能导致性能下降。Executing:这意味着 Goroutine 已经被放置在M上并且正在执行它的指令。
系统调用
对于大部分的 Blocking 系统调用,都是直接将这个阻塞的 G 从 M 卸载下来,换上其他的 G 进行运行。略有不同的,对于异步的系统调用 GO 实现一种 NetPoller 机制,这些操作并不在 M 进行操作,会将这个 Go 移动到 Netpoller 进行操作。这里其实也就是高性能 Web 的核心,不要在 Blocking 进行阻塞。

CGo 的低效问题
在 dave 的 cgo is not go 中写到了
C doesn’t know anything about Go’s calling convention or growable stacks, so a call down to C code must record all the details of the goroutine stack, switch to the C stack, and run C code which has no knowledge of how it was invoked, or the larger Go runtime in charge of the program.
Cgo在编译的时候会为代码生成大量的中间文件。 在一个Go源文件中,如果出现了import "C"指令则表示将调用cgo命令生成对应的中间文件。整个过程会涉及到大量的 堆栈切换
1 | Go --> runtime.cgocall --> runtime.entersyscall --> runtime.asmcgocall --> _cgo_Cfunc_f |
Go 的常见姿势
Channel 关闭
因为关闭一个 Channel 之后,依然可以从中获得数据,因此一个适用的原则是不要从接收端关闭channel,也不要关闭有多个并发发送者的channel。
- channel 只有一个发送方的情况:直接等到发送完毕关不即可
- channel 有多个发送方一个接收方: 额外增加一个 channel ,让接收方通知发送方关闭,因为消息只有一次,因此只有一个人可以关闭
- channel 有多个发送方以及多个接收方: 增加一个调停者,等条件满足的时候通知发送方关闭即可。
其他
- go defer: FILO 特性,不过值得注意的是在
Return之前,PAIN可以看做一种Retrun - chan 缓冲/无缓冲:channel
无缓冲时,发送阻塞直到数据被接收,接收阻塞直到读到数据。channel有缓冲时,当缓冲满时发送阻塞,当缓冲空时接收阻塞。