写点什么

【建议收藏】整理 Golang 面试第二篇干货 13 问

作者:利志分享
  • 2022 年 4 月 27 日
  • 本文字数:2965 字

    阅读完需:约 10 分钟

问:数组和切片的相同点和区别

相同点:

  1. 只能存储一组相同类型的数据结构

  2. 都是通过下标来访问,并且有容量长度,长度通过 len 获取,容量通过 cap 获取

区别:

  1. 数组是定长,访问和复制不能超过数组定义的长度,否则就会下标越界,切片长度和容量可以自动扩充

  2. 数组是值类型,切片是引用类型,每个切片都引用了一个底层数组,切片本身不能存储任何数据,都是这底层数组存储数据,所以修改切片的时候修改的是底层数组中的数据。切片一旦扩容,指向一个新的底层数组内存地址也就随之改变。

问:for range 的时候它的地址会发生变化么?

答:在 for a,b := range c 遍历中, a 和 b 在内存中只会存在一份,即之后每次循环时遍历到的数据都是以值覆盖的方式赋给 a 和 b,a,b 的内存地址始终不变。由于有这个特性,for 循环里面如果开协程,不要直接把 a 或者 b 的地址传给协程。

问:Go 多返回值怎么实现的

答:Go 传参和返回值是通过 FP+offset 实现,并且存储在调用函数的栈帧中。FP 栈底寄存器,指向一个函数栈的顶部;PC 程序计数器,指向下一条执行指令;SB 指向静态数据的基指针,全局符号;SP 栈顶寄存器。

问:map 相关的一些问题

问:map 使用注意的点,并发安全?并发不安全,如果出现两个以上的协程写同一个 map 会报错,使用读写读写锁解决。

问:map 循环是有序的还是无序的?无序的

问:map 中删除一个 key,它的内存会释放么?通过 delete 删除 map 的 key,执行 gc 不会,内存没有被释放,如果通过 map=nil,内存才会释放

问:怎么处理对 map 进行并发访问?通过加读写锁 RWMutex,也可以使用 sync.Map

问:nil map 和空 map 有何不同?nil map 是未初始化的 map,空 map 是长度为空

问:哪些方式安全读写共享变量

答:

  1. 将共享变量的读写放到一个 goroutine 中,其它 goroutine 通过 channel 进行读写操作。

  2. 可以用个数为 1 的信号量(semaphore)实现互斥

  3. 通过 Mutex 锁实现

问:Go 如何实现原子操作

答:原子操作就是不可中断的操作,外界是看不到原子操作的中间状态,要么看到原子操作已经完成,要么看到原子操作已经结束。在某个值的原子操作执行的过程中,CPU 绝对不会再去执行其他针对该值的操作,那么其他操作也是原子操作。

Go 语言的标准库代码包 sync/atomic 提供了原子的读取(Load 为前缀的函数)或写入(Store 为前缀的函数)某个值

原子操作与互斥锁的区别

  1. 互斥锁是一种数据结构,用来让一个线程执行程序的关键部分,完成互斥的多个操作。

  2. 原子操作是针对某个值的单个互斥操作。

问:Mutex 是悲观锁还是乐观锁?悲观锁、乐观锁是什么?

答:Mutex 是悲观锁

悲观锁:当要对数据库中的一条数据进行修改的时候,为了避免同时被其他人修改,最好的办法就是直接对该数据进行加锁以防止并发。这种借助数据库锁机制,在修改数据之前先锁定,再修改的方式被称之为悲观并发控制【Pessimistic Concurrency Control,缩写“PCC”,又名“悲观锁”】。

乐观锁:乐观锁是相对悲观锁而言的,乐观锁假设数据一般情况不会造成冲突,所以在数据进行提交更新的时候,才会正式对数据的冲突与否进行检测,如果冲突,则返回给用户异常信息,让用户决定如何去做。乐观锁适用于读多写少的场景,这样可以提高程序的吞吐量。

问:Mutex 有几种模式?

sync.Mutex 有两种模式,正常模式和饥饿模式。

正常模式:等待的 goroutines 按照 FIFO(先进先出)顺序排队,但是 goroutine 被唤醒之后并不能立即得到 mutex 锁,它需要与新到达的 goroutine 争夺 mutex 锁。因为新到达的 goroutine 已经在 CPU 上运行了,所以被唤醒的 goroutine 很大概率是争夺 mutex 锁是失败 的。出现这样的情况时候,被唤醒 goroutine 需要排队在队列的前面。如果被唤醒的 goroutine 有超过 1ms 没有获取到 mutex 锁,那么它就会变为饥饿模式。在饥饿模式中,mutex 锁直接从解锁的 goroutine 交给队列前面的 goroutine。新达到的 goroutine 也不会去争夺 mutex 锁(即使没有锁,也不能去自旋),而是到等待队列尾部排队。正常模式有更好的性能,因为 goroutine 可以连续多次获得 mutex 锁。

饥饿模式:锁的所有权将从 unlock 的 gorutine 直接交给交给等待队列中的第一个。新来的 goroutine 将不会尝试去获得锁,即使锁看起来是 unlock 状态,也不会去尝试自旋操作,而是放在等待队列的尾部。如果有一个等待的 goroutine 获取到 mutex 锁了,如果它满足下条件中的任意一个, mutex 将会切换回去正常模式:是等待队列中的最后一个 goroutine 和它的等待时间不超过 1ms。饥饿模式能阻止尾部延迟的现象,对于预防队列尾部 goroutine 一致无法获取 mutex 锁的问题。

问:goroutine 的自旋占用资源如何解决?

答:自旋锁是指当一个线程在获取锁的时候,如果锁已经被其他线程获取,那么该线程将循环等待,然后不断地判断是否能够被成功获取,直到获取到锁才会退出循环。

自旋的条件如下:

  1. 还没自旋超过 4 次,

  2. 多核处理器,

  3. GOMAXPROCS > 1,

  4. p 上本地 goroutine 队列为空。

mutex 会让当前的 goroutine 去空转 CPU,在空转完后再次调用 CAS 方法去尝试性的占有锁资源,直到不满足自旋条件,则最终会加入到等待队列里。

问:谈谈内存泄漏,什么情况下内存会泄漏?怎么定位排查内存泄漏问题?

答:go 中的内存泄漏一般都是 goroutine 泄漏,就是 goroutine 没有被关闭,或者没有添加超时控制,让 goroutine 一只处于阻塞状态,不能被 GC。

内存泄露有下面一些情况

  1. 如果 goroutine 在执行时被阻塞而无法退出,就会导致 goroutine 的内存泄漏,一个 goroutine 的最低栈大小为 2KB,在高并发的场景下,对内存的消耗也是非常恐怖的。

  2. 互斥锁未释放或者造成死锁会造成内存泄漏

  3. time.Ticker 是每隔指定的时间就会向通道内写数据。作为循环触发器,必须调用 stop 方法才会停止,从而被 GC 掉,否则会一直占用内存空间。

  4. 字符串的截取引发临时性的内存泄漏

func main() {
 var str0 = "12345678901234567890"
 str1 := str0[:10]
}

  1. 切片截取引起子切片内存泄漏

func main() {
   var s0 = []int{0,1,2,3,4,5,6,7,8,9}
   s1 := s0[:3]
}

  1. 函数数组传参引发内存泄漏【如果我们在函数传参的时候用到了数组传参,且这个数组够大(我们假设数组大小为 100 万,64 位机上消耗的内存约为 800w 字节,即 8MB 内存),或者该函数短时间内被调用 N 次,那么可想而知,会消耗大量内存,对性能产生极大的影响,如果短时间内分配大量内存,而又来不及 GC,那么就会产生临时性的内存泄漏,对于高并发场景相当可怕。】

排查方式:一般通过 pprof 是 Go 的性能分析工具,在程序运行过程中,可以记录程序的运行信息,可以是 CPU 使用情况、内存使用情况、goroutine 运行情况等,当需要性能调优或者定位 Bug 时候,这些记录的信息是相当重要。

问:请简述 Go 是如何分配内存的?

Go 程序启动的时候申请一大块内存,并且划分 spans,bitmap,areana 区域;arena 区域按照页划分成一个个小块,span 管理一个或者多个页,mcentral 管理多个 span 供现场申请使用;mcache 作为线程私有资源,来源于 mcentral。

问:Channel 分配在栈上还是堆上?

Channel 被设计用来实现协程间通信的组件,其作用域和生命周期不可能仅限于某个函数内部,所以 golang 直接将其分配在堆上。

问:介绍一下大对象小对象,为什么小对象多了会造成 gc 压力?

小于等于 32k 的对象就是小对象,其它都是大对象。一般小对象通过 mspan 分配内存;大对象则直接由 mheap 分配内存。通常小对象过多会导致 GC 三色法消耗过多的 CPU。优化思路是,减少对象分配。


参考文献:

书籍《go 专家编程》

发布于: 刚刚阅读数: 2
用户头像

利志分享

关注

专注架构,go,kafka,clickhouse,k8s 2019.03.05 加入

微信公众号:利志分享 或 talk_lizhi;个人小站:zengzhihai.com,分享技术,职场,人生感悟等。

评论

发布
暂无评论
【建议收藏】整理Golang面试第二篇干货13问_golang_利志分享_InfoQ写作社区