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有缓冲
时,当缓冲满时发送阻塞,当缓冲空时接收阻塞。