Go 面试汇总

bnUSE.png

作为一个跨界的 Java Boy 面对 Go 的岗位还是先整理一波。

Go 的 Goroutine

CSP,全称Communicating Sequential Processes,意为通讯顺序进程,它是七大并发模型中的一种,它的核心观念是将两个并发执行的实体通过通道channel连接起来,所有的消息都通过channel传输,Go语言对CSP并发模型的实现——GPM调度模型。

GPM代表了三个角色,分别是GoroutineProcessorMachine

bnRyC.png

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

bnW7t.png

MachineProcessor1:1,我们创建的 Goroutine 会通过放入 QueueProcessorMachine 执行。
不过值得注意,在实现的过程中,有一种名为 work-stealing 的算法,如果某个 MachineQueue 为空的时候会尝试从其他地方偷点回来。

Java体系中: JDK1.7引入的Fork/Join框架就是基于工作窃取算法。

单纯的从图上看,我们至少提出几个问题

  • Goroutine 如何调用了 Systemcall 在阻塞状态会怎么样
  • Goroutine 如何保证时间公平

Go的调度

OS 调度器是一个抢占式调度器。Go 调度器在内核之上的用户空间中运行。Go 调度器的当前实现不是抢占式调度器,而是协作式调度器。

Go 的状态也有三种状态:

  • Waiting:这意味着 Goroutine 已停止并等待一些事情以继续。这可能是因为等待操作系统(系统调用)或同步调用(原子和互斥操作)等原因。这些类型的延迟是性能下降的根本原因。
  • Runnable :这意味着 Goroutine 需要M上的时间片,来执行它的指令。如果同一时间有很多 Goroutines 在竞争时间片,它们都必须等待更长时间才能得到时间片,而且每个 Goroutine 获得的时间片都缩短了。这种类型的调度延迟也可能导致性能下降。
  • Executing :这意味着 Goroutine 已经被放置在M上并且正在执行它的指令。

系统调用

对于大部分的 Blocking 系统调用,都是直接将这个阻塞的 GM 卸载下来,换上其他的 G 进行运行。略有不同的,对于异步的系统调用 GO 实现一种 NetPoller 机制,这些操作并不在 M 进行操作,会将这个 Go 移动到 Netpoller 进行操作。这里其实也就是高性能 Web 的核心,不要在 Blocking 进行阻塞。

bnVVD.png

CGo 的低效问题

davecgo 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
2
3
4
Go --> runtime.cgocall --> runtime.entersyscall --> runtime.asmcgocall --> _cgo_Cfunc_f
|
|
Go <-- runtime.exitsyscall <-- runtime.cgocall <-- runtime.asmcgocall <----------+

Go 的常见姿势

Channel 关闭

因为关闭一个 Channel 之后,依然可以从中获得数据,因此一个适用的原则是不要从接收端关闭channel,也不要关闭有多个并发发送者的channel。

  • channel 只有一个发送方的情况:直接等到发送完毕关不即可
  • channel 有多个发送方一个接收方: 额外增加一个 channel ,让接收方通知发送方关闭,因为消息只有一次,因此只有一个人可以关闭
  • channel 有多个发送方以及多个接收方: 增加一个调停者,等条件满足的时候通知发送方关闭即可。

其他

  • go defer: FILO 特性,不过值得注意的是在 Return 之前,PAIN 可以看做一种 Retrun
  • chan 缓冲/无缓冲:channel无缓冲时,发送阻塞直到数据被接收,接收阻塞直到读到数据。channel有缓冲时,当缓冲满时发送阻塞,当缓冲空时接收阻塞。

参考