interview
常见的一些 Golang 面试题,八股文
选自牛客网,掘金等网站
目录
- 协程
- map
- Mutex
- Channel
- 垃圾回收
协程
- 进程、线程、协程的比较
- GMP模型
- goroutine 自旋占用cpu如何解决(go调用、gmp)
- goroutine 抢占时机(gc 栈扫描)
Map
- Golang Map 底层实现
- 如何实现Map的有序查找(利用一个辅助slice)
- map 的并发安全
- Map 可以用数组作为Key吗(数组可以,切片不可以)
make,new
- make 和 new 的区别
- make 返回的是类型,new返回的是指针
- make 只用于 chan,map,slice 的初始化
- new 用于给类型分配内存空间,并且置零
Array,Slice
- Slice 和 Array 的区别
- array 是定长的,slice 是不定长,可以动态扩容
- slice 包含对 array 的引用,len,cap
- array 是会在编译时 panic,而 slice 是运行时,具体需要看场景使用
- array 是值赋值的,如果量级很大,会消耗一定内存
Mutex
- Mutex 与 RWMutex 怎么实现
Channel
- 向为 nil 的 channel 发送数据会怎么样
▪ 给⼀个 nil channel 发送数据,造成永远阻塞 ▪ 从⼀个 nil channel 接收数据,造成永远阻塞 ▪ 给⼀个已经关闭的 channel 发送数据,引起 panic ▪ 从⼀个已经关闭的 channel 接收数据,如果缓冲区中为空,则返回⼀个零值 ▪ ⽆缓冲的channel是同步的,⽽有缓冲的channel是⾮同步的
15字⼝诀: 空(nil)读写阻塞,写关闭异常,读关闭空零
-
channel 是否线程安全?
-
介绍一下 channel,无缓冲和有缓冲区别
-
是否了解 channel 底层实现,比如实现 channel 的数据结构是什么?
两双向列表的队列,有缓冲的是循环数组
- Channel 分配在栈上还是堆上?哪些对象分配在堆上,哪些对象分配在栈上?
Go 程序会在 2 个地方为变量分配内存,一个是全局的堆(heap)空间用来动态分配内存,另一个是每个 goroutine 的栈(stack)空间。与 Java、Python 等语言类似,Go 语言实现垃圾回收(Garbage Collector)机制,因此呢,Go 语言的内存管理是自动的,通常开发者并不需要关心内存分配在栈上,还是堆上。但是从性能的角度出发,在栈上分配内存和在堆上分配内存,性能差异是非常大的。
在函数中申请一个对象,如果分配在栈中,函数执行结束时自动回收,如果分配在堆中,则在函数结束后某个时间点进行垃圾回收。
在栈上分配和回收内存的开销很低,只需要 2 个 CPU 指令:PUSH 和 POP,一个是将数据 push 到栈空间以完成分配,pop 则是释放空间,也就是说在栈上分配内存,消耗的仅是将数据拷贝到内存的时间,而内存的 I/O 通常能够达到 30GB/s,因此在栈上分配内存效率是非常高的。
堆内存:由内存分配器和垃圾收集器负责回收
栈内存:由编译器自动进行分配和释放
垃圾回收
- Gc触发时机
- 是否了解其他gc机制
- Go 的垃圾回收是怎么实现的,好在哪里,不好在哪里。
内存逃逸
什么是内存逃逸?
在程序中,每个函数块都会有自己的内存区域用来存自己的局部变量(内存占用少)、返回地址、返回值之类的数据,这一块内存区域有特定的结构和寻址方式,寻址起来十分迅速,开销很少。这一块内存地址称为栈。栈是线程级别的,大小在创建的时候已经确定,当变量太大的时候,会"逃逸"到堆上,这种现象称为内存逃逸。简单来说,局部变量通过堆分配和回收,就叫内存逃逸。
build 的时候,增加 -gcflags=-m 参数
-
发生内存逃逸的情况
能引起变量逃逸到堆上的典型情况:
- 在方法内把局部变量指针返回 局部变量原本应该在栈中分配,在栈中回收。但是由于返回时被外部引用,因此其生命周期大于栈,则溢出。
- 发送指针或带有指针的值到 channel 中。 在编译时,是没有办法知道哪个 goroutine 会在 channel 上接收数据。所以编译器没法知道变量什么时候才会被释放。
- 在一个切片上存储指针或带指针的值。 一个典型的例子就是 []*string 。这会导致切片的内容逃逸。尽管其后面的数组可能是在栈上分配的,但其引用的值一定是在堆上。
- slice 的背后数组被重新分配了,因为 append 时可能会超出其容量( cap )。 slice 初始化的地方在编译时是可以知道的,它最开始会在栈上分配。如果切片背后的存储要基于运行时的数据进行扩充,就会在堆上分配。
- 在 interface 类型上调用方法。 在 interface 类型上调用方法都是动态调度的 —— 方法的真正实现只能在运行时知道。想像一个 io.Reader 类型的变量 r , 调用 r.Read(b) 会使得 r 的值和切片b 的背后存储都逃逸掉,所以会在堆上分配。
-
什么内容会分配在堆上
-
怎么避免内存逃逸?
noescape
- init 函数是什么时候执行的
参考链接
golang 面试总结
https://developer.aliyun.com/article/865593
GitHub:
Golang 面试题搜集
https://github.com/lifei6671/interview-go
https://github.com/xiaobaiTech/golangFamily
https://github.com/iswbm/golang-interview
GolangStudy: Golang 面试学习