Go高频面试题

Go高频面试知识点…

1.关于select

  • select是Go语言中的一个控制语句,类似于switch,但是select主要用于通道操作(每一个case必须是一个通信操作,要么接收,要么发送)。

  • select会随机选择一个可以运行的case;如果没有case可运行,那么将会阻塞,直到有一个case可以运行。

2.变量会怎么分配:

变量分配在栈还是堆是由go编译器自己控制的;编译时,会做逃逸分析,当变量作用域没有跑出函数的范围,就放在栈上;反之则必须放在堆上。Go内存逃逸分析

3.GC垃圾回收

  • v1.3:标记清除:整体过程需要启动STW,效率极低。

  • v1.5:三色标记法:堆空间启动写屏障,栈空间不启动,全部扫描之后,需要重新扫描一次栈(需要STW),效率普通

  • v1.8:混合写屏障机制:
    栈空间不启动,堆空间启动。整个过程几乎不需要STW,效率较高 Go垃圾回收

4.协程调度GPM

G:就是个goroutine(调度系统最基本的单位),里面除了存放本goroutine信息(执行stack信息;状态信息;所在的任务函数信息等)与所在P的绑定等信息。

P:就是process,管理着一组goroutine队列,存储当前goroutine运行的上下文环境。

M:machine,代表着真正的执行计算资源。

P管理着一组G在M上执行。GPM轻量理解

  • 每一个运行的M都必须绑定一个P,线程M创建后会检查并执行G对象

  • 每一个P都保存着一个G队列

  • 除了P保存的G队列外,还有一个全局G队列

  • M从队列中取出G并执行

  • P的个数就是 GOMAXPROCS(256),一般不做修改

  • M的个数和P的个数不一定一样多

  • P是一个全局数组(255)来保存,并且维护者一个全局的P空闲链表

5.runtime机制

runtime机制

runtime负责:管理调度,垃圾回收以及运行环境等。。Go语言的goroutine,channel这些高级功能都需要runtime的支持。

如何运行:用户编译代码后,runtime和编译后的代码会被静态链接起来,形成一个可执行文件。runtime通过接口函数调用来管理goroutine,channel这些高级功能;而且从用户代码调用操作系统的API调用会被runtime拦截处理。

组成:一个重要组成部分是goroutine scheduler ,它负责追踪调度goroutine运行,实际上是从应用程序的process所属的thread
pool(线程池)中分配一个thread来执行这个goroutine。因此,每个goroutine只有分配到一个OS thread才能运行。

6.make和new的区别

区别

make:返回初始化后的值。make是引用类型初始化的方法;常用于slice,map,channle。make(T…)

new:返回指针,即new(T)返回的是*T

7.channel有缓冲和无缓冲

有无缓冲

无缓冲:channel接收阻数据塞直到读取数据;channel发送阻塞直到数据被接收

有缓冲:当缓冲满时,channel接收数据阻塞;当缓冲空时,channel发送数据阻塞

无缓冲的 就是一个送信人去你家门口送信 ,你不在家 他不走,你一定要接下信,他才会走。

无缓冲保证信能到你手上(同步)

有缓冲的 就是一个送信人去你家仍到你家的信箱 转身就走 ,除非你的信箱满了 他必须等信箱空下来。

有缓冲的 保证 信能进你家的邮箱(非同步)

8.并发方式和退出

并发:通常使用无缓冲chan来实现goroutine来是实现

退出:使用for-range来退出;使用,ok来退出;使用退出通道来退出

1
2
3
4
5
6
7
8
9
10
11
12
// for-range是使用频率很高的结构,常用它来遍历数据,**range能够感知channel的关闭,当channel被发送数据的协程关闭时,range就会结束**,接着退出for循环。
func main() {
inCh := make(chan int)

go func(in <-chan int) {
// Using for-range to exit goroutine
// range has the ability to detect the close/end of a channel
for x := range in {
fmt.Printf("Process %d\n", x)
}
}(inCh)
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
// 使用一个专门的通道,发送退出的信号
func worker(stopCh <-chan struct{}) {
go func() {
defer fmt.Println("worker exit")
// Using stop channel explicit exit
for {
select {
case <-stopCh:
fmt.Println("Recv stop signal")
return
case <-t.C:
fmt.Println("Working .")
}
}
}()
return
}

9.interface()技巧

  • 空接口 可以存放任意类型的数据
1
2
3
4
5
6
7
8
9
10
11
12
13
func main() {
data := make([]interface{}, 3)
intData := 1
stringData := "abc"
boolData := true
data[0] = intData
data[1] = stringData
data[2] = boolData
for _, v := range data {
fmt.Println(v)
}
}

  • 接口嵌套
1
2
3
4
5
6
7
8
9
10
11
12
13
type ReadWrite interface {
Read(b Buffer) bool
Write(b Buffer) bool
}
type Lock interface {
Lock()
Unlock()
}
type File interface { // 除了上面两个还有一个额外的close()接口
ReadWrite
Lock
Close()
}
  • 类型选择和断言
1
2
3
4
5
6
if v, ok := varI.(T); ok {  // 判断是否是自己想要的类型
assertion
Process(v)
return
}

10.用信道实现主程等待协程2s,如果超过2s,主程直接结束(不用sleep)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
func main() {
start := time.Now()
wait := make(chan int, 1)

go func() {
fmt.Println("做点东西")
time.Sleep(3* time.Second)
wait <- 2
}()

fmt.Println("这里是主程序")

select {
case nums := <-wait: // 当超过(阻塞)2s后,不会再执行这个条件,直接执行下面的
fmt.Println(nums)
case <-time.After(2 * time.Second):
fmt.Println("2秒后")
}
fmt.Println(time.Since(start))
}

Go高频面试题
https://zhyyao.me/2021/09/02/technology/golang/interview_issue/
作者
zhyyao
发布于
2021年9月2日
许可协议