跳到主要内容

interview

常见的一些 Golang 面试题,八股文

选自牛客网,掘金等网站

目录

  • 协程
  • map
  • Mutex
  • Channel
  • 垃圾回收

协程

  1. 进程、线程、协程的比较
  2. GMP模型
  3. goroutine 自旋占用cpu如何解决(go调用、gmp)
  4. goroutine 抢占时机(gc 栈扫描)

Map

  1. Golang Map 底层实现
  2. 如何实现Map的有序查找(利用一个辅助slice)
  3. map 的并发安全
  4. Map 可以用数组作为Key吗(数组可以,切片不可以)

make,new

  1. make 和 new 的区别
  1. make 返回的是类型,new返回的是指针
  2. make 只用于 chan,map,slice 的初始化
  3. new 用于给类型分配内存空间,并且置零

Array,Slice

  1. Slice 和 Array 的区别
  1. array 是定长的,slice 是不定长,可以动态扩容
  2. slice 包含对 array 的引用,len,cap
  3. array 是会在编译时 panic,而 slice 是运行时,具体需要看场景使用
  4. array 是值赋值的,如果量级很大,会消耗一定内存

Mutex

  1. Mutex 与 RWMutex 怎么实现

Channel

  1. 向为 nil 的 channel 发送数据会怎么样

▪ 给⼀个 nil channel 发送数据,造成永远阻塞 ▪ 从⼀个 nil channel 接收数据,造成永远阻塞 ▪ 给⼀个已经关闭的 channel 发送数据,引起 panic ▪ 从⼀个已经关闭的 channel 接收数据,如果缓冲区中为空,则返回⼀个零值 ▪ ⽆缓冲的channel是同步的,⽽有缓冲的channel是⾮同步的

15字⼝诀: 空(nil)读写阻塞,写关闭异常,读关闭空零

  1. channel 是否线程安全?

  2. 介绍一下 channel,无缓冲和有缓冲区别

  3. 是否了解 channel 底层实现,比如实现 channel 的数据结构是什么?

两双向列表的队列,有缓冲的是循环数组

  1. Channel 分配在栈上还是堆上?哪些对象分配在堆上,哪些对象分配在栈上?

Go 程序会在 2 个地方为变量分配内存,一个是全局的堆(heap)空间用来动态分配内存,另一个是每个 goroutine 的栈(stack)空间。与 Java、Python 等语言类似,Go 语言实现垃圾回收(Garbage Collector)机制,因此呢,Go 语言的内存管理是自动的,通常开发者并不需要关心内存分配在栈上,还是堆上。但是从性能的角度出发,在栈上分配内存和在堆上分配内存,性能差异是非常大的。

在函数中申请一个对象,如果分配在栈中,函数执行结束时自动回收,如果分配在堆中,则在函数结束后某个时间点进行垃圾回收。

在栈上分配和回收内存的开销很低,只需要 2 个 CPU 指令:PUSH 和 POP,一个是将数据 push 到栈空间以完成分配,pop 则是释放空间,也就是说在栈上分配内存,消耗的仅是将数据拷贝到内存的时间,而内存的 I/O 通常能够达到 30GB/s,因此在栈上分配内存效率是非常高的。

堆内存:由内存分配器和垃圾收集器负责回收

栈内存:由编译器自动进行分配和释放

垃圾回收

  1. Gc触发时机
  2. 是否了解其他gc机制
  3. Go 的垃圾回收是怎么实现的,好在哪里,不好在哪里。

内存逃逸

什么是内存逃逸?

在程序中,每个函数块都会有自己的内存区域用来存自己的局部变量(内存占用少)、返回地址、返回值之类的数据,这一块内存区域有特定的结构和寻址方式,寻址起来十分迅速,开销很少。这一块内存地址称为栈。栈是线程级别的,大小在创建的时候已经确定,当变量太大的时候,会"逃逸"到堆上,这种现象称为内存逃逸。简单来说,局部变量通过堆分配和回收,就叫内存逃逸。

build 的时候,增加 -gcflags=-m 参数

  1. 发生内存逃逸的情况

能引起变量逃逸到堆上的典型情况

  • 在方法内把局部变量指针返回 局部变量原本应该在栈中分配,在栈中回收。但是由于返回时被外部引用,因此其生命周期大于栈,则溢出。
  • 发送指针或带有指针的值到 channel 中。 在编译时,是没有办法知道哪个 goroutine 会在 channel 上接收数据。所以编译器没法知道变量什么时候才会被释放。
  • 在一个切片上存储指针或带指针的值。 一个典型的例子就是 []*string 。这会导致切片的内容逃逸。尽管其后面的数组可能是在栈上分配的,但其引用的值一定是在堆上。
  • slice 的背后数组被重新分配了,因为 append 时可能会超出其容量( cap )。 slice 初始化的地方在编译时是可以知道的,它最开始会在栈上分配。如果切片背后的存储要基于运行时的数据进行扩充,就会在堆上分配。
  • 在 interface 类型上调用方法。 在 interface 类型上调用方法都是动态调度的 —— 方法的真正实现只能在运行时知道。想像一个 io.Reader 类型的变量 r , 调用 r.Read(b) 会使得 r 的值和切片b 的背后存储都逃逸掉,所以会在堆上分配。
  1. 什么内容会分配在堆上

  2. 怎么避免内存逃逸

noescape

  1. 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 面试学习

https://github.com/cnymw/GolangStudy